Update kine and dynamiclistener

This commit is contained in:
Darren Shepherd 2019-11-08 21:45:10 +00:00
parent e2431bdf9d
commit b3336f69cf
196 changed files with 20007 additions and 438 deletions

14
go.mod
View File

@ -31,6 +31,7 @@ replace (
github.com/prometheus/client_model => github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910
github.com/prometheus/common => github.com/prometheus/common v0.0.0-20181126121408-4724e9255275
github.com/prometheus/procfs => github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a
github.com/rancher/kine => ../kine
k8s.io/api => github.com/rancher/kubernetes/staging/src/k8s.io/api v1.16.2-k3s.1
k8s.io/apiextensions-apiserver => github.com/rancher/kubernetes/staging/src/k8s.io/apiextensions-apiserver v1.16.2-k3s.1
k8s.io/apimachinery => github.com/rancher/kubernetes/staging/src/k8s.io/apimachinery v1.16.2-k3s.1
@ -64,6 +65,7 @@ require (
github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e // indirect
github.com/bronze1man/goStrongswanVici v0.0.0-20190828090544-27d02f80ba40 // indirect
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect
github.com/canonical/go-dqlite v1.1.0
github.com/containerd/cgroups v0.0.0-20190923161937-abd0b19954a6 // indirect
github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 // indirect
@ -75,30 +77,32 @@ require (
github.com/containernetworking/plugins v0.8.2 // indirect
github.com/coreos/flannel v0.11.0
github.com/coreos/go-iptables v0.4.2
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
github.com/docker/docker v0.7.3-0.20190731001754-589f1dad8dad
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/libnetwork v0.8.0-dev.2.0.20190624125649-f0e46a78ea34 // indirect
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4 // indirect
github.com/go-bindata/go-bindata v3.1.2+incompatible
github.com/go-sql-driver/mysql v1.4.1
github.com/gofrs/flock v0.7.1 // indirect
github.com/gogo/googleapis v1.3.0 // indirect
github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2
github.com/gorilla/mux v1.7.3
github.com/gorilla/websocket v1.4.0
github.com/gorilla/websocket v1.4.1
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d // indirect
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 // indirect
github.com/kubernetes-sigs/cri-tools v0.0.0-00010101000000-000000000000
github.com/lib/pq v1.1.1
github.com/lxc/lxd v0.0.0-20191108214106-60ea15630455
github.com/mattn/go-sqlite3 v1.10.0
github.com/mindprince/gonvml v0.0.0-20190828220739-9ebdce4bb989 // indirect
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830
github.com/pkg/errors v0.8.1
github.com/rakelkar/gonetsh v0.0.0-20190719023240-501daadcadf8 // indirect
github.com/rancher/dynamiclistener v0.1.1-0.20191108205817-245f86cc340a
github.com/rancher/dynamiclistener v0.1.1-0.20191110035254-aaa5bc0d2a07
github.com/rancher/helm-controller v0.2.2
github.com/rancher/kine v0.1.2-0.20191107225357-527576e3452f
github.com/rancher/kine v0.2.0
github.com/rancher/remotedialer v0.2.0
github.com/rancher/wrangler v0.2.0
github.com/rancher/wrangler-api v0.2.0
@ -108,11 +112,11 @@ require (
github.com/tchap/go-patricia v2.3.0+incompatible // indirect
github.com/theckman/go-flock v0.7.1 // indirect
github.com/urfave/cli v1.21.0
go.etcd.io/bbolt v1.3.3 // indirect
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3
google.golang.org/grpc v1.23.0
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 // indirect
gopkg.in/yaml.v2 v2.2.4
k8s.io/api v0.0.0
k8s.io/apimachinery v0.0.0

57
go.sum
View File

@ -80,6 +80,8 @@ github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/caddyserver/caddy v1.0.3/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ4uw0RzP1E=
github.com/canonical/go-dqlite v1.1.0 h1:vXGVhHrql++q038JVZk17/VZDbvbH5ySWcObWjuxiBQ=
github.com/canonical/go-dqlite v1.1.0/go.mod h1:wp00vfMvPYgNCyxcPdHB5XExmDoCGoPUGymloAQT17Y=
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cespare/prettybench v0.0.0-20150116022406-03b8cfe5406c/go.mod h1:Xe6ZsFhtM8HrDku0pxJ3/Lr51rwykrzgFwpmTzleatY=
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8=
@ -119,6 +121,8 @@ github.com/containernetworking/plugins v0.8.2/go.mod h1:TxALKWZpWL79BC3GOYKJzzXr
github.com/coredns/corefile-migration v1.0.2/go.mod h1:OFwBp/Wc9dJt5cAZzHWMNhK1r5L0p0jDwIBc6j8NC8E=
github.com/coreos/bbolt v1.3.1-coreos.6 h1:uTXKg9gY70s9jMAKdfljFQcuh4e/BXOM+V+d00KFj3A=
github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.15+incompatible h1:+9RjdC18gMxNQVvSiXvObLu29mOFmkgdsB4cRTlV+EE=
@ -135,6 +139,8 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5t
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea h1:n2Ltr3SrfQlf/9nOna1DoGKxLx3qTSI8Ttl6Xrqp6mw=
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/rkt v1.30.0 h1:Kkt6sYeEGKxA3Y7SCrY+nHoXkWed6Jr2BBY42GqMymM=
github.com/coreos/rkt v1.30.0/go.mod h1:O634mlH6U7qk87poQifK6M2rsFNt+FyUTWNMnP1hF1U=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
@ -187,6 +193,8 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZM
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4 h1:GY1+t5Dr9OKADM64SYnQjw/w99HMYvQ0A8/JoUkxVmc=
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@ -201,6 +209,8 @@ github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG
github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE=
github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
@ -318,6 +328,8 @@ github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e h1:KhcknUwkWHKZ
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/cadvisor v0.34.0 h1:No7G6U/TasplR9uNqyc5Jj0Bet5VSYsK5xLygOf4pUw=
github.com/google/cadvisor v0.34.0/go.mod h1:1nql6U13uTHaLYB8rLS5x9IJc2qT6Xd/Tr1sTX6NE48=
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
@ -351,15 +363,21 @@ github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 h1:6TSoaYExHper8PYsJu23GWVNOyYRCSnIFyxKgLSZ54w=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79 h1:lR9ssWAqp9qL0bALxqEEkuudiP1eweOdv9jsRK3e7lE=
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.3.0 h1:HJtP6RRwj2EpPCD/mhAWzSvLL/dFTdPm1UrWwanoFos=
github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.11.2 h1:bUDfHRK8aKGdya+msYJHffDwNxB8Eileyl7Jf2qqYjI=
github.com/grpc-ecosystem/grpc-gateway v1.11.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
@ -375,8 +393,6 @@ github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7U
github.com/heketi/utils v0.0.0-20170317161834-435bc5bdfa64/go.mod h1:RYlF4ghFZPPmk2TC5REt5OFwvfb6lzxFWrTWB+qs28s=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ibuildthecloud/kine v0.1.1 h1:HAqWHrjRDoqQ3+pKuJrk7Sx9nzoW3zotzhxMfAZ4YME=
github.com/ibuildthecloud/kine v0.1.1/go.mod h1:TTWUtUeu7dHQan9BrCtlRbKr9eK7epHqrBFOAae15Bg=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
@ -396,10 +412,13 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc=
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/juju/testing v0.0.0-20190613124551-e81189438503/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc=
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
@ -438,6 +457,8 @@ github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH
github.com/lucas-clemente/quic-clients v0.1.0/go.mod h1:y5xVIEoObKqULIKivu+gD/LU90pL73bTdtQjPBvtCBk=
github.com/lucas-clemente/quic-go v0.10.2/go.mod h1:hvaRS9IHjFLMq76puFJeWNfmn+H70QZ/CXoxqw9bzao=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
github.com/lxc/lxd v0.0.0-20191108214106-60ea15630455 h1:gQQV7It0kjZxMLJkS/+5Mc6w0zM6pKGzl3OS0h2RHrY=
github.com/lxc/lxd v0.0.0-20191108214106-60ea15630455/go.mod h1:2BaZflfwsv8a3uy3/Vw+de4Avn4DSrAiqaHJjCIXMV4=
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@ -527,6 +548,7 @@ github.com/opencontainers/runtime-spec v0.0.0-20180911193056-5684b8af48c1/go.mod
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
github.com/opencontainers/selinux v1.2.2 h1:Kx9J6eDG5/24A6DtUquGSpJQ+m2MUTahn4FtGEe8bFg=
github.com/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@ -562,16 +584,12 @@ github.com/rancher/cri v1.3.0-k3s.2 h1:k2XFyD+ZdsGvNfugdvqD38KSMANT3JmTFULFM2CtI
github.com/rancher/cri v1.3.0-k3s.2/go.mod h1:Ht5T1dIKzm+4NExmb7wDVG6qR+j0xeXIjjhCv1d9geY=
github.com/rancher/cri-tools v1.16.1-k3s.1 h1:iporgQ46noE6dtLzq6fWcIO2qjyPZy2m42d2P+UnGJg=
github.com/rancher/cri-tools v1.16.1-k3s.1/go.mod h1:TEKhKv2EJIZp+p9jnEy4C63g8CosJzsI4kyKKkHag+8=
github.com/rancher/dynamiclistener v0.1.1-0.20191031022009-6224794ef3cb h1:bMoA9UHr1QNTWVrf0fSJCba6YDU1xmt2jmeohpiugKg=
github.com/rancher/dynamiclistener v0.1.1-0.20191031022009-6224794ef3cb/go.mod h1:fs/dxyNcB3YT6W9fVz4bDGfhmSQS17QQup6BIcGF++s=
github.com/rancher/dynamiclistener v0.1.1-0.20191108205817-245f86cc340a h1:yIQXTC2BjGQ4Bt5Y7QhnxNWbbq8e6koH+pFrJL2VsIs=
github.com/rancher/dynamiclistener v0.1.1-0.20191108205817-245f86cc340a/go.mod h1:fs/dxyNcB3YT6W9fVz4bDGfhmSQS17QQup6BIcGF++s=
github.com/rancher/dynamiclistener v0.1.1-0.20191110035254-aaa5bc0d2a07 h1:wR1hnAh7d7ZicsAwDyw2nfvGFDOvPojcfClwA8WGy5g=
github.com/rancher/dynamiclistener v0.1.1-0.20191110035254-aaa5bc0d2a07/go.mod h1:fs/dxyNcB3YT6W9fVz4bDGfhmSQS17QQup6BIcGF++s=
github.com/rancher/flannel v0.11.0-k3s.1 h1:mIwnfWDafjzQgFkZeJ1AkFrrAT3EdBaA1giE0eLJKo8=
github.com/rancher/flannel v0.11.0-k3s.1/go.mod h1:Hn4ZV+eq0LhLZP63xZnxdGwXEoRSxs5sxELxu27M3UA=
github.com/rancher/helm-controller v0.2.2 h1:MUqisy53/Ay1EYOF2uTCYBbGpgtZLNKKrI01BdxIbQo=
github.com/rancher/helm-controller v0.2.2/go.mod h1:0JkL0UjxddNbT4FmLoESarD4Mz8xzA5YlejqJ/U4g+8=
github.com/rancher/kine v0.1.2-0.20191107225357-527576e3452f h1:tUNKo4xpQfR2Qfg/vyI5/pdBaC730Lu/jqTEqgeo83o=
github.com/rancher/kine v0.1.2-0.20191107225357-527576e3452f/go.mod h1:TTWUtUeu7dHQan9BrCtlRbKr9eK7epHqrBFOAae15Bg=
github.com/rancher/kubernetes v1.16.2-k3s.1 h1:+oJEecXgQDkEOD/X8z2YUdYVonbXZtGzXsmtKDPYesg=
github.com/rancher/kubernetes v1.16.2-k3s.1/go.mod h1:SmhGgKfQ30imqjFVj8AI+iW+zSyFsswNErKYeTfgoH0=
github.com/rancher/kubernetes/staging/src/k8s.io/api v1.16.2-k3s.1 h1:2kK5KD6MU86txBYKG+tM6j5zbey02DaIDtwpG5JsfnI=
@ -629,6 +647,7 @@ github.com/rancher/wrangler-api v0.2.0/go.mod h1:zTPdNLZO07KvRaVOx6XQbKBSV55Fnn4
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/robfig/cron v1.1.0 h1:jk4/Hud3TTdcrJgUOBgsqrZBarcxl6ADIjSC2iniwLY=
github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rootless-containers/rootlesskit v0.6.0 h1:L7DxVAlaNhg4M/+i2GCl24kRkXO5q81C/lu4jlMz3bE=
@ -657,6 +676,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.3 h1:09wy7WZk4AqO03yH85Ex1X+Uo3vDsil3Fa9AgF8Emss=
github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@ -685,6 +706,8 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8 h1:zLV6q4e8Jv9EHjNg/iHfzwDkCve6Ua5jCygptrtXHvI=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
@ -697,6 +720,8 @@ github.com/thecodeteam/goscaleio v0.1.0/go.mod h1:68sdkZAsK8bvEwBlbQnlLS+xU+hvLY
github.com/timakin/bodyclose v0.0.0-20190407043127-4a873e97b2bb/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
@ -720,6 +745,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 h1:MPPkRncZLN9Kh4MEFmbnK4h3BD7AUmskWv2+EeZJCCs=
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek=
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
@ -730,10 +757,16 @@ go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569 h1:nSQar3Y0E3VQF/VdZ8PTAilaXpER+d7ypdABCrpwMdg=
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df h1:shvkWr0NAZkg4nPuE3XrKP0VuBPijjk3TfX6Y6acFNg=
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15 h1:Z2sc4+v0JHV6Mn4kX1f2a5nruNjmV+Th32sugE8zwz8=
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -768,6 +801,7 @@ golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -798,6 +832,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181004145325-8469e314837c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -829,6 +864,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -864,6 +900,8 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
@ -888,6 +926,9 @@ gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 h1:E846t8CnR+lv5nE+VuiKTDG/v1U2stad0QzddfJC7kY=
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5/go.mod h1:hiOFpYm0ZJbusNj2ywpbrXowU3G8U6GIQzqn2mw1UIE=
gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=

28
vendor/github.com/Rican7/retry/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,28 @@
language: go
go:
- 1.6
- tip
sudo: false
install:
# Get all imported packages
- make install-deps install-deps-dev
# Basic build errors
- make build
script:
# Lint
- make format-lint
- make import-lint
- make copyright-lint
# Run tests
- make test
matrix:
allow_failures:
- go: tip
fast_finish: true

19
vendor/github.com/Rican7/retry/LICENSE generated vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (C) 2016 Trevor N. Suarez (Rican7)
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.

72
vendor/github.com/Rican7/retry/Makefile generated vendored Normal file
View File

@ -0,0 +1,72 @@
# Define some VCS context
PARENT_BRANCH ?= master
# Set a default `min_confidence` value for `golint`
GOLINT_MIN_CONFIDENCE ?= 0.3
# Set flags for `gofmt`
GOFMT_FLAGS ?= -s
all: install-deps build install
clean:
go clean -i -x ./...
build:
go build -v ./...
install:
go install ./...
install-deps:
go get -d -t ./...
install-deps-dev: install-deps
go get github.com/golang/lint/golint
go get golang.org/x/tools/cmd/goimports
update-deps:
go get -d -t -u ./...
update-deps-dev: update-deps
go get -u github.com/golang/lint/golint
go get -u golang.org/x/tools/cmd/goimports
test:
go test -v ./...
test-with-coverage:
go test -cover ./...
test-with-coverage-formatted:
go test -cover ./... | column -t | sort -r
format-lint:
errors=$$(gofmt -l ${GOFMT_FLAGS} .); if [ "$${errors}" != "" ]; then echo "$${errors}"; exit 1; fi
import-lint:
errors=$$(goimports -l .); if [ "$${errors}" != "" ]; then echo "$${errors}"; exit 1; fi
style-lint:
errors=$$(golint -min_confidence=${GOLINT_MIN_CONFIDENCE} ./...); if [ "$${errors}" != "" ]; then echo "$${errors}"; exit 1; fi
copyright-lint:
@old_dates=$$(git diff --diff-filter=ACMRTUXB --name-only "${PARENT_BRANCH}" | xargs grep -E '[Cc]opyright(\s+)[©Cc]?(\s+)[0-9]{4}' | grep -E -v "[Cc]opyright(\s+)[©Cc]?(\s+)$$(date '+%Y')"); if [ "$${old_dates}" != "" ]; then printf "The following files contain outdated copyrights:\n$${old_dates}\n\nThis can be fixed with 'make copyright-fix'\n"; exit 1; fi
lint: install-deps-dev format-lint import-lint style-lint copyright-lint
format-fix:
gofmt -w ${GOFMT_FLAGS} .
import-fix:
goimports -w .
copyright-fix:
@git diff --diff-filter=ACMRTUXB --name-only "${PARENT_BRANCH}" | xargs -I '_FILENAME' -- sh -c 'sed -i.bak "s/\([Cc]opyright\([[:space:]][©Cc]\{0,1\}[[:space:]]*\)\)[0-9]\{4\}/\1"$$(date '+%Y')"/g" _FILENAME && rm _FILENAME.bak'
vet:
go vet ./...
.PHONY: all clean build install install-deps install-deps-dev update-deps update-deps-dev test test-with-coverage test-with-coverage-formatted format-lint import-lint style-lint copyright-lint lint format-fix import-fix copyright-fix vet

98
vendor/github.com/Rican7/retry/README.md generated vendored Normal file
View File

@ -0,0 +1,98 @@
# retry
[![Build Status](https://travis-ci.org/Rican7/retry.svg?branch=master)](https://travis-ci.org/Rican7/retry)
[![GoDoc](https://godoc.org/github.com/Rican7/retry?status.png)](https://godoc.org/github.com/Rican7/retry)
[![Go Report Card](https://goreportcard.com/badge/Rican7/retry)](http://goreportcard.com/report/Rican7/retry)
[![Latest Stable Version](https://img.shields.io/github/release/Rican7/retry.svg?style=flat)](https://github.com/Rican7/retry/releases)
A simple, stateless, functional mechanism to perform actions repetitively until successful.
## Project Status
This project is currently in "pre-release". While the code is heavily tested, the API may change.
Vendor (commit or lock) this dependency if you plan on using it.
## Install
`go get github.com/Rican7/retry`
## Examples
### Basic
```go
Retry(func(attempt uint) error {
return nil // Do something that may or may not cause an error
})
```
### File Open
```go
const logFilePath = "/var/log/myapp.log"
var logFile *os.File
err := Retry(func(attempt uint) error {
var err error
logFile, err = os.Open(logFilePath)
return err
})
if nil != err {
log.Fatalf("Unable to open file %q with error %q", logFilePath, err)
}
```
### HTTP request with strategies and backoff
```go
var response *http.Response
action := func(attempt uint) error {
var err error
response, err = http.Get("https://api.github.com/repos/Rican7/retry")
if nil == err && nil != response && response.StatusCode > 200 {
err = fmt.Errorf("failed to fetch (attempt #%d) with status code: %d", attempt, response.StatusCode)
}
return err
}
err := Retry(
action,
strategy.Limit(5),
strategy.Backoff(backoff.Fibonacci(10*time.Millisecond)),
)
if nil != err {
log.Fatalf("Failed to fetch repository with error %q", err)
}
```
### Retry with backoff jitter
```go
action := func(attempt uint) error {
return errors.New("something happened")
}
seed := time.Now().UnixNano()
random := rand.New(rand.NewSource(seed))
Retry(
action,
strategy.Limit(5),
strategy.BackoffWithJitter(
backoff.BinaryExponential(10*time.Millisecond),
jitter.Deviation(random, 0.5),
),
)
```

67
vendor/github.com/Rican7/retry/backoff/backoff.go generated vendored Normal file
View File

@ -0,0 +1,67 @@
// Package backoff provides stateless methods of calculating durations based on
// a number of attempts made.
//
// Copyright © 2016 Trevor N. Suarez (Rican7)
package backoff
import (
"math"
"time"
)
// Algorithm defines a function that calculates a time.Duration based on
// the given retry attempt number.
type Algorithm func(attempt uint) time.Duration
// Incremental creates a Algorithm that increments the initial duration
// by the given increment for each attempt.
func Incremental(initial, increment time.Duration) Algorithm {
return func(attempt uint) time.Duration {
return initial + (increment * time.Duration(attempt))
}
}
// Linear creates a Algorithm that linearly multiplies the factor
// duration by the attempt number for each attempt.
func Linear(factor time.Duration) Algorithm {
return func(attempt uint) time.Duration {
return (factor * time.Duration(attempt))
}
}
// Exponential creates a Algorithm that multiplies the factor duration by
// an exponentially increasing factor for each attempt, where the factor is
// calculated as the given base raised to the attempt number.
func Exponential(factor time.Duration, base float64) Algorithm {
return func(attempt uint) time.Duration {
return (factor * time.Duration(math.Pow(base, float64(attempt))))
}
}
// BinaryExponential creates a Algorithm that multiplies the factor
// duration by an exponentially increasing factor for each attempt, where the
// factor is calculated as `2` raised to the attempt number (2^attempt).
func BinaryExponential(factor time.Duration) Algorithm {
return Exponential(factor, 2)
}
// Fibonacci creates a Algorithm that multiplies the factor duration by
// an increasing factor for each attempt, where the factor is the Nth number in
// the Fibonacci sequence.
func Fibonacci(factor time.Duration) Algorithm {
return func(attempt uint) time.Duration {
return (factor * time.Duration(fibonacciNumber(attempt)))
}
}
// fibonacciNumber calculates the Fibonacci sequence number for the given
// sequence position.
func fibonacciNumber(n uint) uint {
if 0 == n {
return 0
} else if 1 == n {
return 1
} else {
return fibonacciNumber(n-1) + fibonacciNumber(n-2)
}
}

89
vendor/github.com/Rican7/retry/jitter/jitter.go generated vendored Normal file
View File

@ -0,0 +1,89 @@
// Package jitter provides methods of transforming durations.
//
// Copyright © 2016 Trevor N. Suarez (Rican7)
package jitter
import (
"math"
"math/rand"
"time"
)
// Transformation defines a function that calculates a time.Duration based on
// the given duration.
type Transformation func(duration time.Duration) time.Duration
// Full creates a Transformation that transforms a duration into a result
// duration in [0, n) randomly, where n is the given duration.
//
// The given generator is what is used to determine the random transformation.
// If a nil generator is passed, a default one will be provided.
//
// Inspired by https://www.awsarchitectureblog.com/2015/03/backoff.html
func Full(generator *rand.Rand) Transformation {
random := fallbackNewRandom(generator)
return func(duration time.Duration) time.Duration {
return time.Duration(random.Int63n(int64(duration)))
}
}
// Equal creates a Transformation that transforms a duration into a result
// duration in [n/2, n) randomly, where n is the given duration.
//
// The given generator is what is used to determine the random transformation.
// If a nil generator is passed, a default one will be provided.
//
// Inspired by https://www.awsarchitectureblog.com/2015/03/backoff.html
func Equal(generator *rand.Rand) Transformation {
random := fallbackNewRandom(generator)
return func(duration time.Duration) time.Duration {
return (duration / 2) + time.Duration(random.Int63n(int64(duration))/2)
}
}
// Deviation creates a Transformation that transforms a duration into a result
// duration that deviates from the input randomly by a given factor.
//
// The given generator is what is used to determine the random transformation.
// If a nil generator is passed, a default one will be provided.
//
// Inspired by https://developers.google.com/api-client-library/java/google-http-java-client/backoff
func Deviation(generator *rand.Rand, factor float64) Transformation {
random := fallbackNewRandom(generator)
return func(duration time.Duration) time.Duration {
min := int64(math.Floor(float64(duration) * (1 - factor)))
max := int64(math.Ceil(float64(duration) * (1 + factor)))
return time.Duration(random.Int63n(max-min) + min)
}
}
// NormalDistribution creates a Transformation that transforms a duration into a
// result duration based on a normal distribution of the input and the given
// standard deviation.
//
// The given generator is what is used to determine the random transformation.
// If a nil generator is passed, a default one will be provided.
func NormalDistribution(generator *rand.Rand, standardDeviation float64) Transformation {
random := fallbackNewRandom(generator)
return func(duration time.Duration) time.Duration {
return time.Duration(random.NormFloat64()*standardDeviation + float64(duration))
}
}
// fallbackNewRandom returns the passed in random instance if it's not nil,
// and otherwise returns a new random instance seeded with the current time.
func fallbackNewRandom(random *rand.Rand) *rand.Rand {
// Return the passed in value if it's already not null
if nil != random {
return random
}
seed := time.Now().UnixNano()
return rand.New(rand.NewSource(seed))
}

36
vendor/github.com/Rican7/retry/retry.go generated vendored Normal file
View File

@ -0,0 +1,36 @@
// Package retry provides a simple, stateless, functional mechanism to perform
// actions repetitively until successful.
//
// Copyright © 2016 Trevor N. Suarez (Rican7)
package retry
import "github.com/Rican7/retry/strategy"
// Action defines a callable function that package retry can handle.
type Action func(attempt uint) error
// Retry takes an action and performs it, repetitively, until successful.
//
// Optionally, strategies may be passed that assess whether or not an attempt
// should be made.
func Retry(action Action, strategies ...strategy.Strategy) error {
var err error
for attempt := uint(0); (0 == attempt || nil != err) && shouldAttempt(attempt, strategies...); attempt++ {
err = action(attempt)
}
return err
}
// shouldAttempt evaluates the provided strategies with the given attempt to
// determine if the Retry loop should make another attempt.
func shouldAttempt(attempt uint, strategies ...strategy.Strategy) bool {
shouldAttempt := true
for i := 0; shouldAttempt && i < len(strategies); i++ {
shouldAttempt = shouldAttempt && strategies[i](attempt)
}
return shouldAttempt
}

85
vendor/github.com/Rican7/retry/strategy/strategy.go generated vendored Normal file
View File

@ -0,0 +1,85 @@
// Package strategy provides a way to change the way that retry is performed.
//
// Copyright © 2016 Trevor N. Suarez (Rican7)
package strategy
import (
"time"
"github.com/Rican7/retry/backoff"
"github.com/Rican7/retry/jitter"
)
// Strategy defines a function that Retry calls before every successive attempt
// to determine whether it should make the next attempt or not. Returning `true`
// allows for the next attempt to be made. Returning `false` halts the retrying
// process and returns the last error returned by the called Action.
//
// The strategy will be passed an "attempt" number on each successive retry
// iteration, starting with a `0` value before the first attempt is actually
// made. This allows for a pre-action delay, etc.
type Strategy func(attempt uint) bool
// Limit creates a Strategy that limits the number of attempts that Retry will
// make.
func Limit(attemptLimit uint) Strategy {
return func(attempt uint) bool {
return (attempt <= attemptLimit)
}
}
// Delay creates a Strategy that waits the given duration before the first
// attempt is made.
func Delay(duration time.Duration) Strategy {
return func(attempt uint) bool {
if 0 == attempt {
time.Sleep(duration)
}
return true
}
}
// Wait creates a Strategy that waits the given durations for each attempt after
// the first. If the number of attempts is greater than the number of durations
// provided, then the strategy uses the last duration provided.
func Wait(durations ...time.Duration) Strategy {
return func(attempt uint) bool {
if 0 < attempt && 0 < len(durations) {
durationIndex := int(attempt - 1)
if len(durations) <= durationIndex {
durationIndex = len(durations) - 1
}
time.Sleep(durations[durationIndex])
}
return true
}
}
// Backoff creates a Strategy that waits before each attempt, with a duration as
// defined by the given backoff.Algorithm.
func Backoff(algorithm backoff.Algorithm) Strategy {
return BackoffWithJitter(algorithm, noJitter())
}
// BackoffWithJitter creates a Strategy that waits before each attempt, with a
// duration as defined by the given backoff.Algorithm and jitter.Transformation.
func BackoffWithJitter(algorithm backoff.Algorithm, transformation jitter.Transformation) Strategy {
return func(attempt uint) bool {
if 0 < attempt {
time.Sleep(transformation(algorithm(attempt)))
}
return true
}
}
// noJitter creates a jitter.Transformation that simply returns the input.
func noJitter() jitter.Transformation {
return func(duration time.Duration) time.Duration {
return duration
}
}

8
vendor/github.com/canonical/go-dqlite/.dir-locals.el generated vendored Normal file
View File

@ -0,0 +1,8 @@
;;; Directory Local Variables
;;; For more information see (info "(emacs) Directory Variables")
((go-mode
. ((go-test-args . "-tags libsqlite3 -timeout 10s")
(eval
. (set
(make-local-variable 'flycheck-go-build-tags)
'("libsqlite3"))))))

4
vendor/github.com/canonical/go-dqlite/.gitignore generated vendored Normal file
View File

@ -0,0 +1,4 @@
.sqlite
demo
profile.coverprofile
overalls.coverprofile

31
vendor/github.com/canonical/go-dqlite/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,31 @@
dist: xenial
language: go
addons:
apt:
sources:
- sourceline: 'ppa:dqlite/master'
packages:
- golint
- libsqlite3-dev
- libuv1-dev
- libraft-dev
- libco-dev
- libdqlite-dev
before_install:
- go get github.com/go-playground/overalls
- go get github.com/mattn/goveralls
- go get github.com/tsenart/deadcode
script:
- go get -t -tags libsqlite3 ./...
- go vet -tags libsqlite3 ./...
- golint
- deadcode
- project=github.com/canonical/go-dqlite
- $GOPATH/bin/overalls -project $project -covermode=count -- -tags libsqlite3 -timeout 240s
- $GOPATH/bin/goveralls -coverprofile overalls.coverprofile -service=travis-ci
go:
- "1.12"

1
vendor/github.com/canonical/go-dqlite/AUTHORS generated vendored Normal file
View File

@ -0,0 +1 @@
Free Ekanayaka <free.ekanayaka@canonical.com>

201
vendor/github.com/canonical/go-dqlite/LICENSE generated vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

91
vendor/github.com/canonical/go-dqlite/README.md generated vendored Normal file
View File

@ -0,0 +1,91 @@
go-dqlite [![Build Status](https://travis-ci.org/canonical/go-dqlite.png)](https://travis-ci.org/canonical/go-dqlite) [![Coverage Status](https://coveralls.io/repos/github/canonical/go-dqlite/badge.svg?branch=master)](https://coveralls.io/github/canonical/go-dqlite?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/canonical/go-dqlite)](https://goreportcard.com/report/github.com/canonical/go-dqlite) [![GoDoc](https://godoc.org/github.com/canonical/go-dqlite?status.svg)](https://godoc.org/github.com/canonical/go-dqlite)
======
This repository provides the `go-dqlite` Go package, containing bindings for the
[dqlite](https://github.com/canonical/dqlite) C library and a pure-Go
client for the dqlite wire [protocol](https://github.com/canonical/dqlite/blob/master/doc/protocol.md).
Usage
-----
The best way to understand how to use the ```go-dqlite``` package is probably by
looking at the source code of the [demo
program](https://github.com/canonical/go-dqlite/tree/master/cmd/dqlite-demo) and
use it as example.
Build
-----
In order to use the go-dqlite package in your application, you'll need to have
the [dqlite](https://github.com/canonical/dqlite) C library installed on your
system, along with its dependencies. You then need to pass the ```-tags```
argument to the Go tools when building or testing your packages, for example:
```bash
go build -tags libsqlite3
go test -tags libsqlite3
```
Documentation
-------------
The documentation for this package can be found on [Godoc](http://godoc.org/github.com/canonical/go-dqlite).
Demo
----
To see dqlite in action, either install the Debian package from the PPA:
```bash
sudo add-apt-repository -y ppa:dqlite/v1
sudo apt install dqlite
```
or build the dqlite C library and its dependencies from source, as described
[here](https://github.com/canonical/dqlite#build), and then run:
```
go install -tags libsqlite3 ./cmd/dqlite-demo
```
from the top-level directory of this repository.
Once the ```dqlite-demo``` binary is installed, start three nodes of the demo
application, respectively with IDs ```1```, ```2,``` and ```3```:
```bash
dqlite-demo start 1 &
dqlite-demo start 2 &
dqlite-demo start 3 &
```
The node with ID ```1``` automatically becomes the leader of a single node
cluster, while the nodes with IDs ```2``` and ```3``` are waiting to be notified
what cluster they belong to. Let's make nodes ```2``` and ```3``` join the
cluster:
```bash
dqlite-demo add 2
dqlite-demo add 3
```
Now we can start using the cluster. The demo application is just a simple
key/value store that stores data in a SQLite table. Let's insert a key pair:
```bash
dqlite-demo update my-key my-value
```
and then retrive it from the database:
```bash
dqlite-demo query my-key
```
Currently node ```1``` is the leader. If we stop it and then try to query the
key again we'll notice that the ```query``` command hangs for a bit waiting for
the failover to occur and for another node to step up as leader:
```
kill -TERM %1; sleep 0.1; dqlite-demo query my-key
```

221
vendor/github.com/canonical/go-dqlite/client/client.go generated vendored Normal file
View File

@ -0,0 +1,221 @@
package client
import (
"context"
"encoding/binary"
"io"
"net"
"strings"
"github.com/canonical/go-dqlite/internal/protocol"
"github.com/pkg/errors"
)
// DialFunc is a function that can be used to establish a network connection.
type DialFunc = protocol.DialFunc
// Client speaks the dqlite wire protocol.
type Client struct {
protocol *protocol.Protocol
}
// Option that can be used to tweak client parameters.
type Option func(*options)
type options struct {
DialFunc DialFunc
LogFunc LogFunc
}
// WithDialFunc sets a custom dial function for creating the client network
// connection.
func WithDialFunc(dial DialFunc) Option {
return func(options *options) {
options.DialFunc = dial
}
}
// WithLogFunc sets a custom log function.
// connection.
func WithLogFunc(log LogFunc) Option {
return func(options *options) {
options.LogFunc = log
}
}
// New creates a new client connected to the dqlite node with the given
// address.
func New(ctx context.Context, address string, options ...Option) (*Client, error) {
o := defaultOptions()
for _, option := range options {
option(o)
}
// Establish the connection.
conn, err := o.DialFunc(ctx, address)
if err != nil {
return nil, errors.Wrap(err, "failed to establish network connection")
}
// Latest protocol version.
proto := make([]byte, 8)
binary.LittleEndian.PutUint64(proto, protocol.VersionOne)
// Perform the protocol handshake.
n, err := conn.Write(proto)
if err != nil {
conn.Close()
return nil, errors.Wrap(err, "failed to send handshake")
}
if n != 8 {
conn.Close()
return nil, errors.Wrap(io.ErrShortWrite, "failed to send handshake")
}
client := &Client{protocol: protocol.NewProtocol(protocol.VersionOne, conn)}
return client, nil
}
// Leader returns information about the current leader, if any.
func (c *Client) Leader(ctx context.Context) (*NodeInfo, error) {
request := protocol.Message{}
request.Init(16)
response := protocol.Message{}
response.Init(512)
protocol.EncodeLeader(&request)
if err := c.protocol.Call(ctx, &request, &response); err != nil {
return nil, errors.Wrap(err, "failed to send Leader request")
}
id, address, err := protocol.DecodeNode(&response)
if err != nil {
return nil, errors.Wrap(err, "failed to parse Node response")
}
info := &NodeInfo{ID: id, Address: address}
return info, nil
}
// Cluster returns information about all nodes in the cluster.
func (c *Client) Cluster(ctx context.Context) ([]NodeInfo, error) {
request := protocol.Message{}
request.Init(16)
response := protocol.Message{}
response.Init(512)
protocol.EncodeCluster(&request)
if err := c.protocol.Call(ctx, &request, &response); err != nil {
return nil, errors.Wrap(err, "failed to send Cluster request")
}
servers, err := protocol.DecodeNodes(&response)
if err != nil {
return nil, errors.Wrap(err, "failed to parse Node response")
}
return servers, nil
}
// File holds the content of a single database file.
type File struct {
Name string
Data []byte
}
// Dump the content of the database with the given name. Two files will be
// returned, the first is the main database file (which has the same name as
// the database), the second is the WAL file (which has the same name as the
// database plus the suffix "-wal").
func (c *Client) Dump(ctx context.Context, dbname string) ([]File, error) {
request := protocol.Message{}
request.Init(16)
response := protocol.Message{}
response.Init(512)
protocol.EncodeDump(&request, dbname)
if err := c.protocol.Call(ctx, &request, &response); err != nil {
return nil, errors.Wrap(err, "failed to send dump request")
}
files, err := protocol.DecodeFiles(&response)
if err != nil {
return nil, errors.Wrap(err, "failed to parse files response")
}
defer files.Close()
dump := make([]File, 0)
for {
name, data := files.Next()
if name == "" {
break
}
dump = append(dump, File{Name: name, Data: data})
}
return dump, nil
}
// Add a node to a cluster.
func (c *Client) Add(ctx context.Context, node NodeInfo) error {
request := protocol.Message{}
request.Init(4096)
response := protocol.Message{}
response.Init(4096)
protocol.EncodeJoin(&request, node.ID, node.Address)
if err := c.protocol.Call(ctx, &request, &response); err != nil {
return err
}
protocol.EncodePromote(&request, node.ID)
if err := c.protocol.Call(ctx, &request, &response); err != nil {
return err
}
return nil
}
// Remove a node from the cluster.
func (c *Client) Remove(ctx context.Context, id uint64) error {
request := protocol.Message{}
request.Init(4096)
response := protocol.Message{}
response.Init(4096)
protocol.EncodeRemove(&request, id)
if err := c.protocol.Call(ctx, &request, &response); err != nil {
return err
}
return nil
}
// Close the client.
func (c *Client) Close() error {
return c.protocol.Close()
}
// Create a client options object with sane defaults.
func defaultOptions() *options {
return &options{
DialFunc: DefaultDialFunc,
LogFunc: DefaultLogFunc,
}
}
func DefaultDialFunc(ctx context.Context, address string) (net.Conn, error) {
if strings.HasPrefix(address, "@") {
return protocol.UnixDial(ctx, address)
}
return protocol.TCPDial(ctx, address)
}

35
vendor/github.com/canonical/go-dqlite/client/leader.go generated vendored Normal file
View File

@ -0,0 +1,35 @@
package client
import (
"context"
"time"
"github.com/Rican7/retry/backoff"
"github.com/Rican7/retry/strategy"
"github.com/canonical/go-dqlite/internal/protocol"
)
// FindLeader returns a Client connected to the current cluster leader, if any.
func FindLeader(ctx context.Context, store NodeStore, options ...Option) (*Client, error) {
o := defaultOptions()
for _, option := range options {
option(o)
}
config := protocol.Config{
Dial: o.DialFunc,
AttemptTimeout: time.Second,
RetryStrategies: []strategy.Strategy{
strategy.Backoff(backoff.BinaryExponential(time.Millisecond))},
}
connector := protocol.NewConnector(0, store, config, o.LogFunc)
protocol, err := connector.Connect(ctx)
if err != nil {
return nil, err
}
client := &Client{protocol: protocol}
return client, nil
}

30
vendor/github.com/canonical/go-dqlite/client/log.go generated vendored Normal file
View File

@ -0,0 +1,30 @@
package client
import (
"fmt"
"log"
"os"
"github.com/canonical/go-dqlite/internal/logging"
)
// LogFunc is a function that can be used for logging.
type LogFunc = logging.Func
// LogLevel defines the logging level.
type LogLevel = logging.Level
// Available logging levels.
const (
LogDebug = logging.Debug
LogInfo = logging.Info
LogWarn = logging.Warn
LogError = logging.Error
)
// DefaultLogFunc emits messages using the stdlib's logger.
func DefaultLogFunc(l LogLevel, format string, a ...interface{}) {
logger := log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds)
format = fmt.Sprintf("[%s]: %s", l.String(), format)
logger.Printf(format, a...)
}

136
vendor/github.com/canonical/go-dqlite/client/store.go generated vendored Normal file
View File

@ -0,0 +1,136 @@
package client
import (
"context"
"database/sql"
"fmt"
"github.com/pkg/errors"
"github.com/canonical/go-dqlite/internal/protocol"
_ "github.com/mattn/go-sqlite3" // Go SQLite bindings
)
// NodeStore is used by a dqlite client to get an initial list of candidate
// dqlite nodes that it can dial in order to find a leader dqlite node to use.
type NodeStore = protocol.NodeStore
// NodeInfo holds information about a single server.
type NodeInfo = protocol.NodeInfo
// InmemNodeStore keeps the list of target dqlite nodes in memory.
type InmemNodeStore = protocol.InmemNodeStore
// NewInmemNodeStore creates NodeStore which stores its data in-memory.
var NewInmemNodeStore = protocol.NewInmemNodeStore
// DatabaseNodeStore persists a list addresses of dqlite nodes in a SQL table.
type DatabaseNodeStore struct {
db *sql.DB // Database handle to use.
schema string // Name of the schema holding the servers table.
table string // Name of the servers table.
column string // Column name in the servers table holding the server address.
}
// DefaultNodeStore creates a new NodeStore using the given filename to
// open a SQLite database, with default names for the schema, table and column
// parameters.
//
// It also creates the table if it doesn't exist yet.
func DefaultNodeStore(filename string) (*DatabaseNodeStore, error) {
// Open the database.
db, err := sql.Open("sqlite3", filename)
if err != nil {
return nil, errors.Wrap(err, "failed to open database")
}
// Since we're setting SQLite single-thread mode, we need to have one
// connection at most.
db.SetMaxOpenConns(1)
// Create the servers table if it does not exist yet.
_, err = db.Exec("CREATE TABLE IF NOT EXISTS servers (address TEXT, UNIQUE(address))")
if err != nil {
return nil, errors.Wrap(err, "failed to create servers table")
}
store := NewNodeStore(db, "main", "servers", "address")
return store, nil
}
// NewNodeStore creates a new NodeStore.
func NewNodeStore(db *sql.DB, schema, table, column string) *DatabaseNodeStore {
return &DatabaseNodeStore{
db: db,
schema: schema,
table: table,
column: column,
}
}
// Get the current servers.
func (d *DatabaseNodeStore) Get(ctx context.Context) ([]NodeInfo, error) {
tx, err := d.db.Begin()
if err != nil {
return nil, errors.Wrap(err, "failed to begin transaction")
}
defer tx.Rollback()
query := fmt.Sprintf("SELECT %s FROM %s.%s", d.column, d.schema, d.table)
rows, err := tx.QueryContext(ctx, query)
if err != nil {
return nil, errors.Wrap(err, "failed to query servers table")
}
defer rows.Close()
servers := make([]NodeInfo, 0)
for rows.Next() {
var address string
err := rows.Scan(&address)
if err != nil {
return nil, errors.Wrap(err, "failed to fetch server address")
}
servers = append(servers, NodeInfo{ID: 1, Address: address})
}
if err := rows.Err(); err != nil {
return nil, errors.Wrap(err, "result set failure")
}
return servers, nil
}
// Set the servers addresses.
func (d *DatabaseNodeStore) Set(ctx context.Context, servers []NodeInfo) error {
tx, err := d.db.Begin()
if err != nil {
return errors.Wrap(err, "failed to begin transaction")
}
query := fmt.Sprintf("DELETE FROM %s.%s", d.schema, d.table)
if _, err := tx.ExecContext(ctx, query); err != nil {
tx.Rollback()
return errors.Wrap(err, "failed to delete existing servers rows")
}
query = fmt.Sprintf("INSERT INTO %s.%s(%s) VALUES (?)", d.schema, d.table, d.column)
stmt, err := tx.PrepareContext(ctx, query)
if err != nil {
tx.Rollback()
return errors.Wrap(err, "failed to prepare insert statement")
}
defer stmt.Close()
for _, server := range servers {
if _, err := stmt.ExecContext(ctx, server.Address); err != nil {
tx.Rollback()
return errors.Wrapf(err, "failed to insert server %s", server.Address)
}
}
if err := tx.Commit(); err != nil {
return errors.Wrap(err, "failed to commit transaction")
}
return nil
}

670
vendor/github.com/canonical/go-dqlite/driver/driver.go generated vendored Normal file
View File

@ -0,0 +1,670 @@
// Copyright 2017 Canonical Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package driver
import (
"context"
"database/sql/driver"
"io"
"net"
"reflect"
"time"
"github.com/Rican7/retry/backoff"
"github.com/Rican7/retry/strategy"
"github.com/pkg/errors"
"github.com/canonical/go-dqlite/client"
"github.com/canonical/go-dqlite/internal/bindings"
"github.com/canonical/go-dqlite/internal/protocol"
)
// Driver perform queries against a dqlite server.
type Driver struct {
log client.LogFunc // Log function to use
store client.NodeStore // Holds addresses of dqlite servers
context context.Context // Global cancellation context
connectionTimeout time.Duration // Max time to wait for a new connection
contextTimeout time.Duration // Default client context timeout.
clientConfig protocol.Config // Configuration for dqlite client instances
}
// Error is returned in case of database errors.
type Error = bindings.Error
// Error codes. Values here mostly overlap with native SQLite codes.
const (
ErrBusy = 5
errIoErr = 10
errIoErrNotLeader = errIoErr | 32<<8
errIoErrLeadershipLost = errIoErr | (33 << 8)
)
// Option can be used to tweak driver parameters.
type Option func(*options)
// NodeStore is a convenience alias of client.NodeStore.
type NodeStore = client.NodeStore
// NodeInfo is a convenience alias of client.NodeInfo.
type NodeInfo = client.NodeInfo
// DefaultNodeStore is a convenience alias of client.DefaultNodeStore.
var DefaultNodeStore = client.DefaultNodeStore
// WithLogFunc sets a custom logging function.
func WithLogFunc(log client.LogFunc) Option {
return func(options *options) {
options.Log = log
}
}
// DialFunc is a function that can be used to establish a network connection
// with a dqlite node.
type DialFunc = protocol.DialFunc
// WithDialFunc sets a custom dial function.
func WithDialFunc(dial DialFunc) Option {
return func(options *options) {
options.Dial = protocol.DialFunc(dial)
}
}
// WithConnectionTimeout sets the connection timeout.
//
// If not used, the default is 5 seconds.
func WithConnectionTimeout(timeout time.Duration) Option {
return func(options *options) {
options.ConnectionTimeout = timeout
}
}
// WithConnectionBackoffFactor sets the exponential backoff factor for retrying
// failed connection attempts.
//
// If not used, the default is 50 milliseconds.
func WithConnectionBackoffFactor(factor time.Duration) Option {
return func(options *options) {
options.ConnectionBackoffFactor = factor
}
}
// WithConnectionBackoffCap sets the maximum connection retry backoff value,
// (regardless of the backoff factor) for retrying failed connection attempts.
//
// If not used, the default is 1 second.
func WithConnectionBackoffCap(cap time.Duration) Option {
return func(options *options) {
options.ConnectionBackoffCap = cap
}
}
// WithContext sets a global cancellation context.
func WithContext(context context.Context) Option {
return func(options *options) {
options.Context = context
}
}
// WithContextTimeout sets the default client context timeout when no context
// deadline is provided.
//
// If not used, the default is 5 seconds.
func WithContextTimeout(timeout time.Duration) Option {
return func(options *options) {
options.ContextTimeout = timeout
}
}
// NewDriver creates a new dqlite driver, which also implements the
// driver.Driver interface.
func New(store client.NodeStore, options ...Option) (*Driver, error) {
o := defaultOptions()
for _, option := range options {
option(o)
}
driver := &Driver{
log: o.Log,
store: store,
context: o.Context,
connectionTimeout: o.ConnectionTimeout,
contextTimeout: o.ContextTimeout,
}
driver.clientConfig.Dial = o.Dial
driver.clientConfig.AttemptTimeout = 5 * time.Second
driver.clientConfig.RetryStrategies = []strategy.Strategy{
driverConnectionRetryStrategy(
o.ConnectionBackoffFactor,
o.ConnectionBackoffCap,
),
}
return driver, nil
}
// Hold configuration options for a dqlite driver.
type options struct {
Log client.LogFunc
Dial protocol.DialFunc
ConnectionTimeout time.Duration
ContextTimeout time.Duration
ConnectionBackoffFactor time.Duration
ConnectionBackoffCap time.Duration
Context context.Context
}
// Create a options object with sane defaults.
func defaultOptions() *options {
return &options{
Log: client.DefaultLogFunc,
Dial: client.DefaultDialFunc,
ConnectionTimeout: 15 * time.Second,
ContextTimeout: 2 * time.Second,
ConnectionBackoffFactor: 50 * time.Millisecond,
ConnectionBackoffCap: time.Second,
Context: context.Background(),
}
}
// Return a retry strategy with jittered exponential backoff, capped at the
// given amount of time.
func driverConnectionRetryStrategy(factor, cap time.Duration) strategy.Strategy {
backoff := backoff.BinaryExponential(factor)
return func(attempt uint) bool {
if attempt > 0 {
duration := backoff(attempt)
if duration > cap {
duration = cap
}
time.Sleep(duration)
}
return true
}
}
// Open establishes a new connection to a SQLite database on the dqlite server.
//
// The given name must be a pure file name without any directory segment,
// dqlite will connect to a database with that name in its data directory.
//
// Query parameters are always valid except for "mode=memory".
//
// If this node is not the leader, or the leader is unknown an ErrNotLeader
// error is returned.
func (d *Driver) Open(uri string) (driver.Conn, error) {
ctx, cancel := context.WithTimeout(d.context, d.connectionTimeout)
defer cancel()
// TODO: generate a client ID.
connector := protocol.NewConnector(0, d.store, d.clientConfig, d.log)
conn := &Conn{
log: d.log,
contextTimeout: d.contextTimeout,
}
var err error
conn.protocol, err = connector.Connect(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to create dqlite connection")
}
conn.protocol.SetContextTimeout(d.contextTimeout)
conn.request.Init(4096)
conn.response.Init(4096)
defer conn.request.Reset()
defer conn.response.Reset()
protocol.EncodeOpen(&conn.request, uri, 0, "volatile")
if err := conn.protocol.Call(ctx, &conn.request, &conn.response); err != nil {
conn.protocol.Close()
return nil, errors.Wrap(err, "failed to open database")
}
conn.id, err = protocol.DecodeDb(&conn.response)
if err != nil {
conn.protocol.Close()
return nil, errors.Wrap(err, "failed to open database")
}
return conn, nil
}
// SetContextTimeout sets the default client timeout when no context deadline
// is provided.
func (d *Driver) SetContextTimeout(timeout time.Duration) {
d.contextTimeout = timeout
}
// ErrNoAvailableLeader is returned as root cause of Open() if there's no
// leader available in the cluster.
var ErrNoAvailableLeader = protocol.ErrNoAvailableLeader
// Conn implements the sql.Conn interface.
type Conn struct {
log client.LogFunc
protocol *protocol.Protocol
request protocol.Message
response protocol.Message
id uint32 // Database ID.
contextTimeout time.Duration
}
// PrepareContext returns a prepared statement, bound to this connection.
// context is for the preparation of the statement, it must not store the
// context within the statement itself.
func (c *Conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
defer c.request.Reset()
defer c.response.Reset()
stmt := &Stmt{
protocol: c.protocol,
request: &c.request,
response: &c.response,
}
protocol.EncodePrepare(&c.request, uint64(c.id), query)
if err := c.protocol.Call(ctx, &c.request, &c.response); err != nil {
return nil, driverError(err)
}
var err error
stmt.db, stmt.id, stmt.params, err = protocol.DecodeStmt(&c.response)
if err != nil {
return nil, driverError(err)
}
return stmt, nil
}
// Prepare returns a prepared statement, bound to this connection.
func (c *Conn) Prepare(query string) (driver.Stmt, error) {
return c.PrepareContext(context.Background(), query)
}
// ExecContext is an optional interface that may be implemented by a Conn.
func (c *Conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
defer c.request.Reset()
defer c.response.Reset()
protocol.EncodeExecSQL(&c.request, uint64(c.id), query, args)
if err := c.protocol.Call(ctx, &c.request, &c.response); err != nil {
return nil, driverError(err)
}
result, err := protocol.DecodeResult(&c.response)
if err != nil {
return nil, driverError(err)
}
return &Result{result: result}, nil
}
// Query is an optional interface that may be implemented by a Conn.
func (c *Conn) Query(query string, args []driver.Value) (driver.Rows, error) {
return c.QueryContext(context.Background(), query, valuesToNamedValues(args))
}
// QueryContext is an optional interface that may be implemented by a Conn.
func (c *Conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
defer c.request.Reset()
protocol.EncodeQuerySQL(&c.request, uint64(c.id), query, args)
if err := c.protocol.Call(ctx, &c.request, &c.response); err != nil {
c.response.Reset()
return nil, driverError(err)
}
rows, err := protocol.DecodeRows(&c.response)
if err != nil {
c.response.Reset()
return nil, driverError(err)
}
return &Rows{ctx: ctx, request: &c.request, response: &c.response, protocol: c.protocol, rows: rows}, nil
}
// Exec is an optional interface that may be implemented by a Conn.
func (c *Conn) Exec(query string, args []driver.Value) (driver.Result, error) {
return c.ExecContext(context.Background(), query, valuesToNamedValues(args))
}
// Close invalidates and potentially stops any current prepared statements and
// transactions, marking this connection as no longer in use.
//
// Because the sql package maintains a free pool of connections and only calls
// Close when there's a surplus of idle connections, it shouldn't be necessary
// for drivers to do their own connection caching.
func (c *Conn) Close() error {
return c.protocol.Close()
}
// BeginTx starts and returns a new transaction. If the context is canceled by
// the user the sql package will call Tx.Rollback before discarding and closing
// the connection.
//
// This must check opts.Isolation to determine if there is a set isolation
// level. If the driver does not support a non-default level and one is set or
// if there is a non-default isolation level that is not supported, an error
// must be returned.
//
// This must also check opts.ReadOnly to determine if the read-only value is
// true to either set the read-only transaction property if supported or return
// an error if it is not supported.
func (c *Conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
if _, err := c.ExecContext(ctx, "BEGIN", nil); err != nil {
return nil, driverError(err)
}
tx := &Tx{
conn: c,
}
return tx, nil
}
// Begin starts and returns a new transaction.
//
// Deprecated: Drivers should implement ConnBeginTx instead (or additionally).
func (c *Conn) Begin() (driver.Tx, error) {
return c.BeginTx(context.Background(), driver.TxOptions{})
}
// Tx is a transaction.
type Tx struct {
conn *Conn
}
// Commit the transaction.
func (tx *Tx) Commit() error {
ctx, cancel := context.WithTimeout(context.Background(), tx.conn.contextTimeout)
defer cancel()
if _, err := tx.conn.ExecContext(ctx, "COMMIT", nil); err != nil {
return driverError(err)
}
return nil
}
// Rollback the transaction.
func (tx *Tx) Rollback() error {
ctx, cancel := context.WithTimeout(context.Background(), tx.conn.contextTimeout)
defer cancel()
if _, err := tx.conn.ExecContext(ctx, "ROLLBACK", nil); err != nil {
return driverError(err)
}
return nil
}
// Stmt is a prepared statement. It is bound to a Conn and not
// used by multiple goroutines concurrently.
type Stmt struct {
protocol *protocol.Protocol
request *protocol.Message
response *protocol.Message
db uint32
id uint32
params uint64
}
// Close closes the statement.
func (s *Stmt) Close() error {
defer s.request.Reset()
defer s.response.Reset()
protocol.EncodeFinalize(s.request, s.db, s.id)
ctx := context.Background()
if err := s.protocol.Call(ctx, s.request, s.response); err != nil {
return driverError(err)
}
if err := protocol.DecodeEmpty(s.response); err != nil {
return driverError(err)
}
return nil
}
// NumInput returns the number of placeholder parameters.
func (s *Stmt) NumInput() int {
return int(s.params)
}
// ExecContext executes a query that doesn't return rows, such
// as an INSERT or UPDATE.
//
// ExecContext must honor the context timeout and return when it is canceled.
func (s *Stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
defer s.request.Reset()
defer s.response.Reset()
protocol.EncodeExec(s.request, s.db, s.id, args)
if err := s.protocol.Call(ctx, s.request, s.response); err != nil {
return nil, driverError(err)
}
result, err := protocol.DecodeResult(s.response)
if err != nil {
return nil, driverError(err)
}
return &Result{result: result}, nil
}
// Exec executes a query that doesn't return rows, such
func (s *Stmt) Exec(args []driver.Value) (driver.Result, error) {
return s.ExecContext(context.Background(), valuesToNamedValues(args))
}
// QueryContext executes a query that may return rows, such as a
// SELECT.
//
// QueryContext must honor the context timeout and return when it is canceled.
func (s *Stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
defer s.request.Reset()
// FIXME: this shouldn't be needed but we have hit a few panics
// probably due to the response object not being fully reset.
s.response.Reset()
protocol.EncodeQuery(s.request, s.db, s.id, args)
if err := s.protocol.Call(ctx, s.request, s.response); err != nil {
s.response.Reset()
return nil, driverError(err)
}
rows, err := protocol.DecodeRows(s.response)
if err != nil {
s.response.Reset()
return nil, driverError(err)
}
return &Rows{ctx: ctx, request: s.request, response: s.response, protocol: s.protocol, rows: rows}, nil
}
// Query executes a query that may return rows, such as a
func (s *Stmt) Query(args []driver.Value) (driver.Rows, error) {
return s.QueryContext(context.Background(), valuesToNamedValues(args))
}
// Result is the result of a query execution.
type Result struct {
result protocol.Result
}
// LastInsertId returns the database's auto-generated ID
// after, for example, an INSERT into a table with primary
// key.
func (r *Result) LastInsertId() (int64, error) {
return int64(r.result.LastInsertID), nil
}
// RowsAffected returns the number of rows affected by the
// query.
func (r *Result) RowsAffected() (int64, error) {
return int64(r.result.RowsAffected), nil
}
// Rows is an iterator over an executed query's results.
type Rows struct {
ctx context.Context
protocol *protocol.Protocol
request *protocol.Message
response *protocol.Message
rows protocol.Rows
consumed bool
}
// Columns returns the names of the columns. The number of
// columns of the result is inferred from the length of the
// slice. If a particular column name isn't known, an empty
// string should be returned for that entry.
func (r *Rows) Columns() []string {
return r.rows.Columns
}
// Close closes the rows iterator.
func (r *Rows) Close() error {
err := r.rows.Close()
// If we consumed the whole result set, there's nothing to do as
// there's no pending response from the server.
if r.consumed {
return nil
}
// If there is was a single-response result set, we're done.
if err == io.EOF {
return nil
}
// Let's issue an interrupt request and wait until we get an empty
// response, signalling that the query was interrupted.
if err := r.protocol.Interrupt(r.ctx, r.request, r.response); err != nil {
return driverError(err)
}
return nil
}
// Next is called to populate the next row of data into
// the provided slice. The provided slice will be the same
// size as the Columns() are wide.
//
// Next should return io.EOF when there are no more rows.
func (r *Rows) Next(dest []driver.Value) error {
err := r.rows.Next(dest)
if err == protocol.ErrRowsPart {
r.rows.Close()
if err := r.protocol.More(r.ctx, r.response); err != nil {
return driverError(err)
}
rows, err := protocol.DecodeRows(r.response)
if err != nil {
return driverError(err)
}
r.rows = rows
return r.rows.Next(dest)
}
if err == io.EOF {
r.consumed = true
}
return err
}
// ColumnTypeScanType implements RowsColumnTypeScanType.
func (r *Rows) ColumnTypeScanType(i int) reflect.Type {
// column := sql.NewColumn(r.rows, i)
// typ, err := r.protocol.ColumnTypeScanType(context.Background(), column)
// if err != nil {
// return nil
// }
// return typ.DriverType()
return nil
}
// ColumnTypeDatabaseTypeName implements RowsColumnTypeDatabaseTypeName.
func (r *Rows) ColumnTypeDatabaseTypeName(i int) string {
// column := sql.NewColumn(r.rows, i)
// typeName, err := r.protocol.ColumnTypeDatabaseTypeName(context.Background(), column)
// if err != nil {
// return ""
// }
// return typeName.Value
return ""
}
// Convert a driver.Value slice into a driver.NamedValue slice.
func valuesToNamedValues(args []driver.Value) []driver.NamedValue {
namedValues := make([]driver.NamedValue, len(args))
for i, value := range args {
namedValues[i] = driver.NamedValue{
Ordinal: i + 1,
Value: value,
}
}
return namedValues
}
func driverError(err error) error {
switch err := errors.Cause(err).(type) {
case *net.OpError:
return driver.ErrBadConn
case protocol.ErrRequest:
switch err.Code {
case errIoErrNotLeader:
fallthrough
case errIoErrLeadershipLost:
return driver.ErrBadConn
default:
return Error{
Code: int(err.Code),
Message: err.Description,
}
}
}
return err
}
func init() {
err := bindings.Init()
if err != nil {
panic(errors.Wrap(err, "failed to initialize dqlite"))
}
}

View File

@ -0,0 +1,6 @@
package bindings
/*
#cgo linux LDFLAGS: -lsqlite3 -lraft -lco -ldqlite
*/
import "C"

View File

@ -0,0 +1,19 @@
package bindings
/*
#include <sqlite3.h>
*/
import "C"
// Error holds information about a SQLite error.
type Error struct {
Code int
Message string
}
func (e Error) Error() string {
if e.Message != "" {
return e.Message
}
return C.GoString(C.sqlite3_errstr(C.int(e.Code)))
}

View File

@ -0,0 +1,252 @@
package bindings
/*
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <stdint.h>
#include <signal.h>
#include <dqlite.h>
#include <raft.h>
#include <sqlite3.h>
#define EMIT_BUF_LEN 1024
typedef unsigned long long nanoseconds_t;
// Duplicate a file descriptor and prevent it from being cloned into child processes.
static int dupCloexec(int oldfd) {
int newfd = -1;
newfd = dup(oldfd);
if (newfd < 0) {
return -1;
}
if (fcntl(newfd, F_SETFD, FD_CLOEXEC) < 0) {
return -1;
}
return newfd;
}
// C to Go trampoline for custom connect function.
int connectWithDial(uintptr_t handle, char *address, int *fd);
// Wrapper to call the Go trampoline.
static int connectTrampoline(void *data, const char *address, int *fd) {
uintptr_t handle = (uintptr_t)(data);
return connectWithDial(handle, (char*)address, fd);
}
// Configure a custom connect function.
static int configConnectFunc(dqlite_node *t, uintptr_t handle) {
return dqlite_node_set_connect_func(t, connectTrampoline, (void*)handle);
}
static int initializeSQLite()
{
int rc;
// Configure SQLite for single-thread mode. This is a global config.
rc = sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);
if (rc != SQLITE_OK) {
assert(rc == SQLITE_MISUSE);
return DQLITE_MISUSE;
}
return 0;
}
static dqlite_node_info *makeInfos(int n) {
return calloc(n, sizeof(dqlite_node_info));
}
static void setInfo(dqlite_node_info *infos, unsigned i, unsigned id, const char *address) {
dqlite_node_info *info = &infos[i];
info->id = id;
info->address = address;
}
*/
import "C"
import (
"context"
"fmt"
"net"
"os"
"sync"
"time"
"unsafe"
"github.com/canonical/go-dqlite/internal/protocol"
)
type Node C.dqlite_node
// Init initializes dqlite global state.
func Init() error {
// FIXME: ignore SIGPIPE, see https://github.com/joyent/libuv/issues/1254
C.signal(C.SIGPIPE, C.SIG_IGN)
// Don't enable single thread mode when running tests. TODO: find a
// better way to expose this functionality.
if os.Getenv("GO_DQLITE_MULTITHREAD") == "1" {
return nil
}
if rc := C.initializeSQLite(); rc != 0 {
return fmt.Errorf("%d", rc)
}
return nil
}
// NewNode creates a new Node instance.
func NewNode(id uint64, address string, dir string) (*Node, error) {
var server *C.dqlite_node
cid := C.unsigned(id)
caddress := C.CString(address)
defer C.free(unsafe.Pointer(caddress))
cdir := C.CString(dir)
defer C.free(unsafe.Pointer(cdir))
if rc := C.dqlite_node_create(cid, caddress, cdir, &server); rc != 0 {
return nil, fmt.Errorf("failed to create task object")
}
return (*Node)(unsafe.Pointer(server)), nil
}
func (s *Node) SetDialFunc(dial protocol.DialFunc) error {
server := (*C.dqlite_node)(unsafe.Pointer(s))
connectLock.Lock()
defer connectLock.Unlock()
connectIndex++
connectRegistry[connectIndex] = dial
if rc := C.configConnectFunc(server, connectIndex); rc != 0 {
return fmt.Errorf("failed to set connect func")
}
return nil
}
func (s *Node) SetBindAddress(address string) error {
server := (*C.dqlite_node)(unsafe.Pointer(s))
caddress := C.CString(address)
defer C.free(unsafe.Pointer(caddress))
if rc := C.dqlite_node_set_bind_address(server, caddress); rc != 0 {
return fmt.Errorf("failed to set bind address")
}
return nil
}
func (s *Node) SetNetworkLatency(nanoseconds uint64) error {
server := (*C.dqlite_node)(unsafe.Pointer(s))
cnanoseconds := C.nanoseconds_t(nanoseconds)
if rc := C.dqlite_node_set_network_latency(server, cnanoseconds); rc != 0 {
return fmt.Errorf("failed to set network latency")
}
return nil
}
func (s *Node) GetBindAddress() string {
server := (*C.dqlite_node)(unsafe.Pointer(s))
return C.GoString(C.dqlite_node_get_bind_address(server))
}
func (s *Node) Start() error {
server := (*C.dqlite_node)(unsafe.Pointer(s))
if rc := C.dqlite_node_start(server); rc != 0 {
return fmt.Errorf("failed to start task")
}
return nil
}
func (s *Node) Stop() error {
server := (*C.dqlite_node)(unsafe.Pointer(s))
if rc := C.dqlite_node_stop(server); rc != 0 {
return fmt.Errorf("task stopped with error code %d", rc)
}
return nil
}
// Close the server releasing all used resources.
func (s *Node) Close() {
server := (*C.dqlite_node)(unsafe.Pointer(s))
C.dqlite_node_destroy(server)
}
func (s *Node) Recover(cluster []protocol.NodeInfo) error {
server := (*C.dqlite_node)(unsafe.Pointer(s))
n := C.int(len(cluster))
infos := C.makeInfos(n)
defer C.free(unsafe.Pointer(infos))
for i, info := range cluster {
cid := C.unsigned(info.ID)
caddress := C.CString(info.Address)
defer C.free(unsafe.Pointer(caddress))
C.setInfo(infos, C.unsigned(i), cid, caddress)
}
if rc := C.dqlite_node_recover(server, infos, n); rc != 0 {
return fmt.Errorf("recover failed with error code %d", rc)
}
return nil
}
// Extract the underlying socket from a connection.
func connToSocket(conn net.Conn) (C.int, error) {
file, err := conn.(fileConn).File()
if err != nil {
return C.int(-1), err
}
fd1 := C.int(file.Fd())
// Duplicate the file descriptor, in order to prevent Go's finalizer to
// close it.
fd2 := C.dupCloexec(fd1)
if fd2 < 0 {
return C.int(-1), fmt.Errorf("failed to dup socket fd")
}
conn.Close()
return fd2, nil
}
// Interface that net.Conn must implement in order to extract the underlying
// file descriptor.
type fileConn interface {
File() (*os.File, error)
}
//export connectWithDial
func connectWithDial(handle C.uintptr_t, address *C.char, fd *C.int) C.int {
connectLock.Lock()
defer connectLock.Unlock()
dial := connectRegistry[handle]
// TODO: make timeout customizable.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
conn, err := dial(ctx, C.GoString(address))
if err != nil {
return C.RAFT_NOCONNECTION
}
socket, err := connToSocket(conn)
if err != nil {
return C.RAFT_NOCONNECTION
}
*fd = socket
return C.int(0)
}
// Use handles to avoid passing Go pointers to C.
var connectRegistry = make(map[C.uintptr_t]protocol.DialFunc)
var connectIndex C.uintptr_t = 100
var connectLock = sync.Mutex{}
// ErrNodeStopped is returned by Node.Handle() is the server was stopped.
var ErrNodeStopped = fmt.Errorf("server was stopped")
// To compare bool values.
var cfalse C.bool

View File

@ -0,0 +1,26 @@
package logging
import (
"fmt"
"testing"
)
// Func is a function that can be used for logging.
type Func func(Level, string, ...interface{})
// Test returns a logging function that forwards messages to the test logger.
func Test(t *testing.T) Func {
return func(l Level, format string, a ...interface{}) {
format = fmt.Sprintf("%s: %s", l.String(), format)
t.Logf(format, a...)
}
}
// Stdout returns a logging function that prints log messages on standard
// output.
func Stdout() Func {
return func(l Level, format string, a ...interface{}) {
format = fmt.Sprintf("%s: %s\n", l.String(), format)
fmt.Printf(format, a...)
}
}

View File

@ -0,0 +1,27 @@
package logging
// Level defines the logging level.
type Level int
// Available logging levels.
const (
Debug Level = iota
Info
Warn
Error
)
func (l Level) String() string {
switch l {
case Debug:
return "DEBUG"
case Info:
return "INFO"
case Warn:
return "WARN"
case Error:
return "ERROR"
default:
return "UNKNOWN"
}
}

View File

@ -0,0 +1,11 @@
package protocol
// Buffer for reading responses or writing requests.
type buffer struct {
Bytes []byte
Offset int
}
func (b *buffer) Advance(amount int) {
b.Offset += amount
}

View File

@ -0,0 +1,14 @@
package protocol
import (
"time"
"github.com/Rican7/retry/strategy"
)
// Config holds various configuration parameters for a dqlite client.
type Config struct {
Dial DialFunc // Network dialer.
AttemptTimeout time.Duration // Timeout for each individual Dial attempt.
RetryStrategies []strategy.Strategy // Strategies used for retrying to connect to a leader.
}

View File

@ -0,0 +1,250 @@
package protocol
import (
"context"
"encoding/binary"
"fmt"
"io"
"net"
"github.com/Rican7/retry"
"github.com/canonical/go-dqlite/internal/logging"
"github.com/pkg/errors"
)
// DialFunc is a function that can be used to establish a network connection.
type DialFunc func(context.Context, string) (net.Conn, error)
// Connector is in charge of creating a dqlite SQL client connected to the
// current leader of a cluster.
type Connector struct {
id uint64 // Conn ID to use when registering against the server.
store NodeStore // Used to get and update current cluster servers.
config Config // Connection parameters.
log logging.Func // Logging function.
}
// NewConnector returns a new connector that can be used by a dqlite driver to
// create new clients connected to a leader dqlite server.
func NewConnector(id uint64, store NodeStore, config Config, log logging.Func) *Connector {
connector := &Connector{
id: id,
store: store,
config: config,
log: log,
}
return connector
}
// Connect finds the leader server and returns a connection to it.
//
// If the connector is stopped before a leader is found, nil is returned.
func (c *Connector) Connect(ctx context.Context) (*Protocol, error) {
var protocol *Protocol
// The retry strategy should be configured to retry indefinitely, until
// the given context is done.
err := retry.Retry(func(attempt uint) error {
log := func(l logging.Level, format string, a ...interface{}) {
format += fmt.Sprintf(" attempt=%d", attempt)
c.log(l, fmt.Sprintf(format, a...))
}
select {
case <-ctx.Done():
// Stop retrying
return nil
default:
}
var err error
protocol, err = c.connectAttemptAll(ctx, log)
if err != nil {
log(logging.Debug, "connection failed err=%v", err)
return err
}
return nil
}, c.config.RetryStrategies...)
if err != nil {
// The retry strategy should never give up until success or
// context expiration.
panic("connect retry aborted unexpectedly")
}
if ctx.Err() != nil {
return nil, ErrNoAvailableLeader
}
return protocol, nil
}
// Make a single attempt to establish a connection to the leader server trying
// all addresses available in the store.
func (c *Connector) connectAttemptAll(ctx context.Context, log logging.Func) (*Protocol, error) {
servers, err := c.store.Get(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to get cluster servers")
}
// Make an attempt for each address until we find the leader.
for _, server := range servers {
log := func(l logging.Level, format string, a ...interface{}) {
format += fmt.Sprintf(" address=%s", server.Address)
log(l, fmt.Sprintf(format, a...))
}
ctx, cancel := context.WithTimeout(ctx, c.config.AttemptTimeout)
defer cancel()
version := VersionOne
protocol, leader, err := c.connectAttemptOne(ctx, server.Address, version)
if err == errBadProtocol {
version = VersionLegacy
protocol, leader, err = c.connectAttemptOne(ctx, server.Address, version)
}
if err != nil {
// This server is unavailable, try with the next target.
log(logging.Debug, "server connection failed err=%v", err)
continue
}
if protocol != nil {
// We found the leader
log(logging.Info, "connected")
return protocol, nil
}
if leader == "" {
// This server does not know who the current leader is,
// try with the next target.
continue
}
// If we get here, it means this server reported that another
// server is the leader, let's close the connection to this
// server and try with the suggested one.
//logger = logger.With(zap.String("leader", leader))
protocol, leader, err = c.connectAttemptOne(ctx, leader, version)
if err != nil {
// The leader reported by the previous server is
// unavailable, try with the next target.
//logger.Info("leader server connection failed", zap.String("err", err.Error()))
continue
}
if protocol == nil {
// The leader reported by the target server does not consider itself
// the leader, try with the next target.
//logger.Info("reported leader server is not the leader")
continue
}
log(logging.Info, "connected")
return protocol, nil
}
return nil, ErrNoAvailableLeader
}
// Connect establishes a connection with a dqlite node.
func Connect(ctx context.Context, dial DialFunc, address string, version uint64) (*Protocol, error) {
// Establish the connection.
conn, err := dial(ctx, address)
if err != nil {
return nil, errors.Wrap(err, "failed to establish network connection")
}
// Latest protocol version.
protocol := make([]byte, 8)
binary.LittleEndian.PutUint64(protocol, version)
// Perform the protocol handshake.
n, err := conn.Write(protocol)
if err != nil {
conn.Close()
return nil, errors.Wrap(err, "failed to send handshake")
}
if n != 8 {
conn.Close()
return nil, errors.Wrap(io.ErrShortWrite, "failed to send handshake")
}
return NewProtocol(version, conn), nil
}
// Connect to the given dqlite server and check if it's the leader.
//
// Return values:
//
// - Any failure is hit: -> nil, "", err
// - Target not leader and no leader known: -> nil, "", nil
// - Target not leader and leader known: -> nil, leader, nil
// - Target is the leader: -> server, "", nil
//
func (c *Connector) connectAttemptOne(ctx context.Context, address string, version uint64) (*Protocol, string, error) {
protocol, err := Connect(ctx, c.config.Dial, address, version)
if err != nil {
return nil, "", err
}
// Send the initial Leader request.
request := Message{}
request.Init(16)
response := Message{}
response.Init(512)
EncodeLeader(&request)
if err := protocol.Call(ctx, &request, &response); err != nil {
protocol.Close()
cause := errors.Cause(err)
// Best-effort detection of a pre-1.0 dqlite node: when sent
// version 1 it should close the connection immediately.
if _, ok := cause.(*net.OpError); ok || cause == io.EOF {
return nil, "", errBadProtocol
}
return nil, "", errors.Wrap(err, "failed to send Leader request")
}
_, leader, err := DecodeNodeCompat(protocol, &response)
if err != nil {
protocol.Close()
return nil, "", errors.Wrap(err, "failed to parse Node response")
}
switch leader {
case "":
// Currently this server does not know about any leader.
protocol.Close()
return nil, "", nil
case address:
// This server is the leader, register ourselves and return.
request.Reset()
response.Reset()
EncodeClient(&request, c.id)
if err := protocol.Call(ctx, &request, &response); err != nil {
protocol.Close()
return nil, "", errors.Wrap(err, "failed to send Conn request")
}
_, err := DecodeWelcome(&response)
if err != nil {
protocol.Close()
return nil, "", errors.Wrap(err, "failed to parse Welcome response")
}
// TODO: enable heartbeat
// protocol.heartbeatTimeout = time.Duration(heartbeatTimeout) * time.Millisecond
//go protocol.heartbeat()
return protocol, "", nil
default:
// This server claims to know who the current leader is.
protocol.Close()
return nil, leader, nil
}
}
var errBadProtocol = fmt.Errorf("bad protocol")

View File

@ -0,0 +1,58 @@
package protocol
// VersionOne is version 1 of the server protocol.
const VersionOne = uint64(1)
// VersionLegacy is the pre 1.0 dqlite server protocol version.
const VersionLegacy = uint64(0x86104dd760433fe5)
// SQLite datatype codes
const (
Integer = 1
Float = 2
Text = 3
Blob = 4
Null = 5
)
// Special data types for time values.
const (
UnixTime = 9
ISO8601 = 10
Boolean = 11
)
// Request types.
const (
RequestLeader = 0
RequestClient = 1
RequestHeartbeat = 2
RequestOpen = 3
RequestPrepare = 4
RequestExec = 5
RequestQuery = 6
RequestFinalize = 7
RequestExecSQL = 8
RequestQuerySQL = 9
RequestInterrupt = 10
RequestJoin = 12
RequestPromote = 13
RequestRemove = 14
RequestDump = 15
RequestCluster = 16
)
// Response types.
const (
ResponseFailure = 0
ResponseNode = 1
ResponseNodeLegacy = 1
ResponseWelcome = 2
ResponseNodes = 3
ResponseDb = 4
ResponseStmt = 5
ResponseResult = 6
ResponseRows = 7
ResponseEmpty = 8
ResponseFiles = 9
)

View File

@ -0,0 +1,20 @@
package protocol
import (
"context"
"net"
)
// TCPDial is a dial function using plain TCP to establish the network
// connection.
func TCPDial(ctx context.Context, address string) (net.Conn, error) {
dialer := net.Dialer{}
return dialer.DialContext(ctx, "tcp", address)
}
// UnixDial is a dial function using Unix sockets to establish the network
// connection.
func UnixDial(ctx context.Context, address string) (net.Conn, error) {
dialer := net.Dialer{}
return dialer.DialContext(ctx, "unix", address)
}

View File

@ -0,0 +1,29 @@
package protocol
import (
"fmt"
)
// Client errors.
var (
ErrNoAvailableLeader = fmt.Errorf("no available dqlite leader server found")
errStop = fmt.Errorf("connector was stopped")
errStaleLeader = fmt.Errorf("server has lost leadership")
errNotClustered = fmt.Errorf("server is not clustered")
errNegativeRead = fmt.Errorf("reader returned negative count from Read")
errMessageEOF = fmt.Errorf("message eof")
)
// ErrRequest is returned in case of request failure.
type ErrRequest struct {
Code uint64
Description string
}
func (e ErrRequest) Error() string {
return fmt.Sprintf("%s (%d)", e.Description, e.Code)
}
// ErrRowsPart is returned when the first batch of a multi-response result
// batch is done.
var ErrRowsPart = fmt.Errorf("not all rows were returned in this response")

View File

@ -0,0 +1,660 @@
package protocol
import (
"bytes"
"database/sql/driver"
"encoding/binary"
"fmt"
"io"
"math"
"strings"
"time"
)
// NamedValues is a type alias of a slice of driver.NamedValue. It's used by
// schema.sh to generate encoding logic for statement parameters.
type NamedValues = []driver.NamedValue
// Nodes is a type alias of a slice of NodeInfo. It's used by schema.sh to
// generate decoding logic for the heartbeat response.
type Nodes []NodeInfo
// Message holds data about a single request or response.
type Message struct {
words uint32
mtype uint8
flags uint8
extra uint16
header []byte // Statically allocated header buffer
body1 buffer // Statically allocated body data, using bytes
body2 buffer // Dynamically allocated body data
}
// Init initializes the message using the given size of the statically
// allocated buffer (i.e. a buffer which is re-used across requests or
// responses encoded or decoded using this message object).
func (m *Message) Init(staticSize int) {
if (staticSize % messageWordSize) != 0 {
panic("static size is not aligned to word boundary")
}
m.header = make([]byte, messageHeaderSize)
m.body1.Bytes = make([]byte, staticSize)
m.Reset()
}
// Reset the state of the message so it can be used to encode or decode again.
func (m *Message) Reset() {
m.words = 0
m.mtype = 0
m.flags = 0
m.extra = 0
for i := 0; i < messageHeaderSize; i++ {
m.header[i] = 0
}
m.body1.Offset = 0
m.body2.Bytes = nil
m.body2.Offset = 0
}
// Append a byte slice to the message.
func (m *Message) putBlob(v []byte) {
size := len(v)
m.putUint64(uint64(size))
pad := 0
if (size % messageWordSize) != 0 {
// Account for padding
pad = messageWordSize - (size % messageWordSize)
size += pad
}
b := m.bufferForPut(size)
defer b.Advance(size)
// Copy the bytes into the buffer.
offset := b.Offset
copy(b.Bytes[offset:], v)
offset += len(v)
// Add padding
for i := 0; i < pad; i++ {
b.Bytes[offset] = 0
offset++
}
}
// Append a string to the message.
func (m *Message) putString(v string) {
size := len(v) + 1
pad := 0
if (size % messageWordSize) != 0 {
// Account for padding
pad = messageWordSize - (size % messageWordSize)
size += pad
}
b := m.bufferForPut(size)
defer b.Advance(size)
// Copy the string bytes into the buffer.
offset := b.Offset
copy(b.Bytes[offset:], v)
offset += len(v)
// Add a nul byte
b.Bytes[offset] = 0
offset++
// Add padding
for i := 0; i < pad; i++ {
b.Bytes[offset] = 0
offset++
}
}
// Append a byte to the message.
func (m *Message) putUint8(v uint8) {
b := m.bufferForPut(1)
defer b.Advance(1)
b.Bytes[b.Offset] = v
}
// Append a 2-byte word to the message.
func (m *Message) putUint16(v uint16) {
b := m.bufferForPut(2)
defer b.Advance(2)
binary.LittleEndian.PutUint16(b.Bytes[b.Offset:], v)
}
// Append a 4-byte word to the message.
func (m *Message) putUint32(v uint32) {
b := m.bufferForPut(4)
defer b.Advance(4)
binary.LittleEndian.PutUint32(b.Bytes[b.Offset:], v)
}
// Append an 8-byte word to the message.
func (m *Message) putUint64(v uint64) {
b := m.bufferForPut(8)
defer b.Advance(8)
binary.LittleEndian.PutUint64(b.Bytes[b.Offset:], v)
}
// Append a signed 8-byte word to the message.
func (m *Message) putInt64(v int64) {
b := m.bufferForPut(8)
defer b.Advance(8)
binary.LittleEndian.PutUint64(b.Bytes[b.Offset:], uint64(v))
}
// Append a floating point number to the message.
func (m *Message) putFloat64(v float64) {
b := m.bufferForPut(8)
defer b.Advance(8)
binary.LittleEndian.PutUint64(b.Bytes[b.Offset:], math.Float64bits(v))
}
// Encode the given driver values as binding parameters.
func (m *Message) putNamedValues(values NamedValues) {
n := uint8(len(values)) // N of params
if n == 0 {
return
}
m.putUint8(n)
for i := range values {
if values[i].Ordinal != i+1 {
panic("unexpected ordinal")
}
switch values[i].Value.(type) {
case int64:
m.putUint8(Integer)
case float64:
m.putUint8(Float)
case bool:
m.putUint8(Boolean)
case []byte:
m.putUint8(Blob)
case string:
m.putUint8(Text)
case nil:
m.putUint8(Null)
case time.Time:
m.putUint8(ISO8601)
default:
panic("unsupported value type")
}
}
b := m.bufferForPut(1)
if trailing := b.Offset % messageWordSize; trailing != 0 {
// Skip padding bytes
b.Advance(messageWordSize - trailing)
}
for i := range values {
switch v := values[i].Value.(type) {
case int64:
m.putInt64(v)
case float64:
m.putFloat64(v)
case bool:
if v {
m.putUint64(1)
} else {
m.putUint64(0)
}
case []byte:
m.putBlob(v)
case string:
m.putString(v)
case nil:
m.putInt64(0)
case time.Time:
timestamp := v.Format(iso8601Formats[0])
m.putString(timestamp)
default:
panic("unsupported value type")
}
}
}
// Finalize the message by setting the message type and the number
// of words in the body (calculated from the body size).
func (m *Message) putHeader(mtype uint8) {
if m.body1.Offset <= 0 {
panic("static offset is not positive")
}
if (m.body1.Offset % messageWordSize) != 0 {
panic("static body is not aligned")
}
m.mtype = mtype
m.flags = 0
m.extra = 0
m.words = uint32(m.body1.Offset) / messageWordSize
if m.body2.Bytes == nil {
m.finalize()
return
}
if m.body2.Offset <= 0 {
panic("dynamic offset is not positive")
}
if (m.body2.Offset % messageWordSize) != 0 {
panic("dynamic body is not aligned")
}
m.words += uint32(m.body2.Offset) / messageWordSize
m.finalize()
}
func (m *Message) finalize() {
if m.words == 0 {
panic("empty message body")
}
binary.LittleEndian.PutUint32(m.header[0:], m.words)
m.header[4] = m.mtype
m.header[5] = m.flags
binary.LittleEndian.PutUint16(m.header[6:], m.extra)
}
func (m *Message) bufferForPut(size int) *buffer {
if m.body2.Bytes != nil {
if (m.body2.Offset + size) > len(m.body2.Bytes) {
// Grow body2.
//
// TODO: find a good grow strategy.
bytes := make([]byte, m.body2.Offset+size)
copy(bytes, m.body2.Bytes)
m.body2.Bytes = bytes
}
return &m.body2
}
if (m.body1.Offset + size) > len(m.body1.Bytes) {
m.body2.Bytes = make([]byte, size)
m.body2.Offset = 0
return &m.body2
}
return &m.body1
}
// Return the message type and its flags.
func (m *Message) getHeader() (uint8, uint8) {
return m.mtype, m.flags
}
// Read a string from the message body.
func (m *Message) getString() string {
b := m.bufferForGet()
index := bytes.IndexByte(b.Bytes[b.Offset:], 0)
if index == -1 {
// Check if the string overflows in the dynamic buffer.
if b == &m.body1 && m.body2.Bytes != nil {
// Assert that this is the first read of the dynamic buffer.
if m.body2.Offset != 0 {
panic("static buffer read after dynamic buffer one")
}
index = bytes.IndexByte(m.body2.Bytes[0:], 0)
if index != -1 {
// We found the trailing part of the string.
data := b.Bytes[b.Offset:]
data = append(data, m.body2.Bytes[0:index]...)
index++
if trailing := index % messageWordSize; trailing != 0 {
// Account for padding, moving index to the next word boundary.
index += messageWordSize - trailing
}
m.body1.Offset = len(m.body1.Bytes)
m.body2.Advance(index)
return string(data)
}
}
panic("no string found")
}
s := string(b.Bytes[b.Offset : b.Offset+index])
index++
if trailing := index % messageWordSize; trailing != 0 {
// Account for padding, moving index to the next word boundary.
index += messageWordSize - trailing
}
b.Advance(index)
return s
}
func (m *Message) getBlob() []byte {
size := m.getUint64()
data := make([]byte, size)
for i := range data {
data[i] = m.getUint8()
}
pad := 0
if (size % messageWordSize) != 0 {
// Account for padding
pad = int(messageWordSize - (size % messageWordSize))
}
// Consume padding
for i := 0; i < pad; i++ {
m.getUint8()
}
return data
}
// Read a byte from the message body.
func (m *Message) getUint8() uint8 {
b := m.bufferForGet()
defer b.Advance(1)
return b.Bytes[b.Offset]
}
// Read a 2-byte word from the message body.
func (m *Message) getUint16() uint16 {
b := m.bufferForGet()
defer b.Advance(2)
return binary.LittleEndian.Uint16(b.Bytes[b.Offset:])
}
// Read a 4-byte word from the message body.
func (m *Message) getUint32() uint32 {
b := m.bufferForGet()
defer b.Advance(4)
return binary.LittleEndian.Uint32(b.Bytes[b.Offset:])
}
// Read reads an 8-byte word from the message body.
func (m *Message) getUint64() uint64 {
b := m.bufferForGet()
defer b.Advance(8)
return binary.LittleEndian.Uint64(b.Bytes[b.Offset:])
}
// Read a signed 8-byte word from the message body.
func (m *Message) getInt64() int64 {
b := m.bufferForGet()
defer b.Advance(8)
return int64(binary.LittleEndian.Uint64(b.Bytes[b.Offset:]))
}
// Read a floating point number from the message body.
func (m *Message) getFloat64() float64 {
b := m.bufferForGet()
defer b.Advance(8)
return math.Float64frombits(binary.LittleEndian.Uint64(b.Bytes[b.Offset:]))
}
// Decode a list of server objects from the message body.
func (m *Message) getNodes() Nodes {
n := m.getUint64()
servers := make(Nodes, n)
for i := 0; i < int(n); i++ {
servers[i].ID = m.getUint64()
servers[i].Address = m.getString()
}
return servers
}
// Decode a statement result object from the message body.
func (m *Message) getResult() Result {
return Result{
LastInsertID: m.getUint64(),
RowsAffected: m.getUint64(),
}
}
// Decode a query result set object from the message body.
func (m *Message) getRows() Rows {
// Read the column count and column names.
columns := make([]string, m.getUint64())
for i := range columns {
columns[i] = m.getString()
}
rows := Rows{
Columns: columns,
message: m,
}
return rows
}
func (m *Message) getFiles() Files {
files := Files{
n: m.getUint64(),
message: m,
}
return files
}
func (m *Message) hasBeenConsumed() bool {
size := int(m.words * messageWordSize)
return (m.body1.Offset == size || m.body1.Offset == len(m.body1.Bytes)) &&
m.body1.Offset+m.body2.Offset == size
}
func (m *Message) lastByte() byte {
size := int(m.words * messageWordSize)
if size > len(m.body1.Bytes) {
size = size - m.body1.Offset
return m.body2.Bytes[size-1]
}
return m.body1.Bytes[size-1]
}
func (m *Message) bufferForGet() *buffer {
size := int(m.words * messageWordSize)
if m.body1.Offset == size || m.body1.Offset == len(m.body1.Bytes) {
// The static body has been exahusted, use the dynamic one.
if m.body1.Offset+m.body2.Offset == size {
err := fmt.Errorf("short message: type=%d words=%d off=%d", m.mtype, m.words, m.body1.Offset)
panic(err)
}
return &m.body2
}
return &m.body1
}
// Result holds the result of a statement.
type Result struct {
LastInsertID uint64
RowsAffected uint64
}
// Rows holds a result set encoded in a message body.
type Rows struct {
Columns []string
message *Message
}
// Next returns the next row in the result set.
func (r *Rows) Next(dest []driver.Value) error {
types := make([]uint8, len(r.Columns))
// Each column needs a 4 byte slot to store the column type. The row
// header must be padded to reach word boundary.
headerBits := len(types) * 4
padBits := 0
if trailingBits := (headerBits % messageWordBits); trailingBits != 0 {
padBits = (messageWordBits - trailingBits)
}
headerSize := (headerBits + padBits) / messageWordBits * messageWordSize
for i := 0; i < headerSize; i++ {
slot := r.message.getUint8()
if slot == 0xee {
// More rows are available.
return ErrRowsPart
}
if slot == 0xff {
// Rows EOF marker
return io.EOF
}
index := i * 2
if index >= len(types) {
continue // This is padding.
}
types[index] = slot & 0x0f
index++
if index >= len(types) {
continue // This is padding byte.
}
types[index] = slot >> 4
}
for i := range types {
switch types[i] {
case Integer:
dest[i] = r.message.getInt64()
case Float:
dest[i] = r.message.getFloat64()
case Blob:
dest[i] = r.message.getBlob()
case Text:
dest[i] = r.message.getString()
case Null:
r.message.getUint64()
dest[i] = nil
case UnixTime:
timestamp := time.Unix(r.message.getInt64(), 0)
dest[i] = timestamp
case ISO8601:
value := r.message.getString()
if value == "" {
dest[i] = time.Time{}
break
}
var t time.Time
var timeVal time.Time
var err error
value = strings.TrimSuffix(value, "Z")
for _, format := range iso8601Formats {
if timeVal, err = time.ParseInLocation(format, value, time.UTC); err == nil {
t = timeVal
break
}
}
if err != nil {
return err
}
t = t.In(time.Local)
dest[i] = t
case Boolean:
dest[i] = r.message.getInt64() != 0
default:
panic("unknown data type")
}
}
return nil
}
// Close the result set and reset the underlying message.
func (r *Rows) Close() error {
// If we didn't go through all rows, let's look at the last byte.
var err error
if !r.message.hasBeenConsumed() {
slot := r.message.lastByte()
if slot == 0xee {
// More rows are available.
err = ErrRowsPart
} else if slot == 0xff {
// Rows EOF marker
err = io.EOF
} else {
err = fmt.Errorf("unexpected end of message")
}
}
r.message.Reset()
return err
}
// Files holds a set of files encoded in a message body.
type Files struct {
n uint64
message *Message
}
func (f *Files) Next() (string, []byte) {
if f.n == 0 {
return "", nil
}
f.n--
name := f.message.getString()
length := f.message.getUint64()
data := make([]byte, length)
for i := 0; i < int(length); i++ {
data[i] = f.message.getUint8()
}
return name, data
}
func (f *Files) Close() {
f.message.Reset()
}
const (
messageWordSize = 8
messageWordBits = messageWordSize * 8
messageHeaderSize = messageWordSize
messageMaxConsecutiveEmptyReads = 100
)
var iso8601Formats = []string{
// By default, store timestamps with whatever timezone they come with.
// When parsed, they will be returned with the same timezone.
"2006-01-02 15:04:05.999999999-07:00",
"2006-01-02T15:04:05.999999999-07:00",
"2006-01-02 15:04:05.999999999",
"2006-01-02T15:04:05.999999999",
"2006-01-02 15:04:05",
"2006-01-02T15:04:05",
"2006-01-02 15:04",
"2006-01-02T15:04",
"2006-01-02",
}

View File

@ -0,0 +1,340 @@
package protocol
import (
"context"
"encoding/binary"
"io"
"net"
"sync"
"time"
"github.com/pkg/errors"
)
// Protocol sends and receive the dqlite message on the wire.
type Protocol struct {
version uint64 // Protocol version
conn net.Conn // Underlying network connection.
contextTimeout time.Duration // Default context timeout.
closeCh chan struct{} // Stops the heartbeat when the connection gets closed
mu sync.Mutex // Serialize requests
netErr error // A network error occurred
}
func NewProtocol(version uint64, conn net.Conn) *Protocol {
protocol := &Protocol{
version: version,
conn: conn,
closeCh: make(chan struct{}),
contextTimeout: 5 * time.Second,
}
return protocol
}
// SetContextTimeout sets the default context timeout when no deadline is
// provided.
func (p *Protocol) SetContextTimeout(timeout time.Duration) {
p.contextTimeout = timeout
}
// Call invokes a dqlite RPC, sending a request message and receiving a
// response message.
func (p *Protocol) Call(ctx context.Context, request, response *Message) (err error) {
// We need to take a lock since the dqlite server currently does not
// support concurrent requests.
p.mu.Lock()
defer p.mu.Unlock()
if p.netErr != nil {
return p.netErr
}
// Honor the ctx deadline, if present, or use a default.
deadline, ok := ctx.Deadline()
if !ok {
deadline = time.Now().Add(p.contextTimeout)
}
p.conn.SetDeadline(deadline)
if err = p.send(request); err != nil {
err = errors.Wrap(err, "failed to send request")
goto err
}
if err = p.recv(response); err != nil {
err = errors.Wrap(err, "failed to receive response")
goto err
}
return
err:
switch errors.Cause(err).(type) {
case *net.OpError:
p.netErr = err
}
return
}
// More is used when a request maps to multiple responses.
func (p *Protocol) More(ctx context.Context, response *Message) error {
return p.recv(response)
}
// Interrupt sends an interrupt request and awaits for the server's empty
// response.
func (p *Protocol) Interrupt(ctx context.Context, request *Message, response *Message) error {
// We need to take a lock since the dqlite server currently does not
// support concurrent requests.
p.mu.Lock()
defer p.mu.Unlock()
// Honor the ctx deadline, if present, or use a default.
deadline, ok := ctx.Deadline()
if !ok {
deadline = time.Now().Add(2 * time.Second)
}
p.conn.SetDeadline(deadline)
defer request.Reset()
EncodeInterrupt(request, 0)
if err := p.send(request); err != nil {
return errors.Wrap(err, "failed to send interrupt request")
}
for {
if err := p.recv(response); err != nil {
response.Reset()
return errors.Wrap(err, "failed to receive response")
}
mtype, _ := response.getHeader()
response.Reset()
if mtype == ResponseEmpty {
break
}
}
return nil
}
// Close the client connection.
func (p *Protocol) Close() error {
close(p.closeCh)
return p.conn.Close()
}
func (p *Protocol) send(req *Message) error {
if err := p.sendHeader(req); err != nil {
return errors.Wrap(err, "failed to send header")
}
if err := p.sendBody(req); err != nil {
return errors.Wrap(err, "failed to send body")
}
return nil
}
func (p *Protocol) sendHeader(req *Message) error {
n, err := p.conn.Write(req.header[:])
if err != nil {
return errors.Wrap(err, "failed to send header")
}
if n != messageHeaderSize {
return errors.Wrap(io.ErrShortWrite, "failed to send header")
}
return nil
}
func (p *Protocol) sendBody(req *Message) error {
buf := req.body1.Bytes[:req.body1.Offset]
n, err := p.conn.Write(buf)
if err != nil {
return errors.Wrap(err, "failed to send static body")
}
if n != len(buf) {
return errors.Wrap(io.ErrShortWrite, "failed to write body")
}
if req.body2.Bytes == nil {
return nil
}
buf = req.body2.Bytes[:req.body2.Offset]
n, err = p.conn.Write(buf)
if err != nil {
return errors.Wrap(err, "failed to send dynamic body")
}
if n != len(buf) {
return errors.Wrap(io.ErrShortWrite, "failed to write body")
}
return nil
}
func (p *Protocol) recv(res *Message) error {
if err := p.recvHeader(res); err != nil {
return errors.Wrap(err, "failed to receive header")
}
if err := p.recvBody(res); err != nil {
return errors.Wrap(err, "failed to receive body")
}
return nil
}
func (p *Protocol) recvHeader(res *Message) error {
if err := p.recvPeek(res.header); err != nil {
return errors.Wrap(err, "failed to receive header")
}
res.words = binary.LittleEndian.Uint32(res.header[0:])
res.mtype = res.header[4]
res.flags = res.header[5]
res.extra = binary.LittleEndian.Uint16(res.header[6:])
return nil
}
func (p *Protocol) recvBody(res *Message) error {
n := int(res.words) * messageWordSize
n1 := n
n2 := 0
if n1 > len(res.body1.Bytes) {
// We need to allocate the dynamic buffer.
n1 = len(res.body1.Bytes)
n2 = n - n1
}
buf := res.body1.Bytes[:n1]
if err := p.recvPeek(buf); err != nil {
return errors.Wrap(err, "failed to read body")
}
if n2 > 0 {
res.body2.Bytes = make([]byte, n2)
res.body2.Offset = 0
buf = res.body2.Bytes
if err := p.recvPeek(buf); err != nil {
return errors.Wrap(err, "failed to read body")
}
}
return nil
}
// Read until buf is full.
func (p *Protocol) recvPeek(buf []byte) error {
for offset := 0; offset < len(buf); {
n, err := p.recvFill(buf[offset:])
if err != nil {
return err
}
offset += n
}
return nil
}
// Try to fill buf, but perform at most one read.
func (p *Protocol) recvFill(buf []byte) (int, error) {
// Read new data: try a limited number of times.
//
// This technique is copied from bufio.Reader.
for i := messageMaxConsecutiveEmptyReads; i > 0; i-- {
n, err := p.conn.Read(buf)
if n < 0 {
panic(errNegativeRead)
}
if err != nil {
return -1, err
}
if n > 0 {
return n, nil
}
}
return -1, io.ErrNoProgress
}
/*
func (p *Protocol) heartbeat() {
request := Message{}
request.Init(16)
response := Message{}
response.Init(512)
for {
delay := c.heartbeatTimeout / 3
//c.logger.Debug("sending heartbeat", zap.Duration("delay", delay))
time.Sleep(delay)
// Check if we've been closed.
select {
case <-c.closeCh:
return
default:
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
EncodeHeartbeat(&request, uint64(time.Now().Unix()))
err := c.Call(ctx, &request, &response)
// We bail out upon failures.
//
// TODO: make the client survive temporary disconnections.
if err != nil {
cancel()
//c.logger.Error("heartbeat failed", zap.Error(err))
return
}
//addresses, err := DecodeNodes(&response)
_, err = DecodeNodes(&response)
if err != nil {
cancel()
//c.logger.Error("invalid heartbeat response", zap.Error(err))
return
}
// if err := c.store.Set(ctx, addresses); err != nil {
// cancel()
// c.logger.Error("failed to update servers", zap.Error(err))
// return
// }
cancel()
request.Reset()
response.Reset()
}
}
*/
// DecodeNodeCompat handles also pre-1.0 legacy server messages.
func DecodeNodeCompat(protocol *Protocol, response *Message) (uint64, string, error) {
if protocol.version == VersionLegacy {
address, err := DecodeNodeLegacy(response)
if err != nil {
return 0, "", err
}
return 0, address, nil
}
return DecodeNode(response)
}

View File

@ -0,0 +1,131 @@
package protocol
// DO NOT EDIT
//
// This file was generated by ./schema.sh
// EncodeLeader encodes a Leader request.
func EncodeLeader(request *Message) {
request.putUint64(0)
request.putHeader(RequestLeader)
}
// EncodeClient encodes a Client request.
func EncodeClient(request *Message, id uint64) {
request.putUint64(id)
request.putHeader(RequestClient)
}
// EncodeHeartbeat encodes a Heartbeat request.
func EncodeHeartbeat(request *Message, timestamp uint64) {
request.putUint64(timestamp)
request.putHeader(RequestHeartbeat)
}
// EncodeOpen encodes a Open request.
func EncodeOpen(request *Message, name string, flags uint64, vfs string) {
request.putString(name)
request.putUint64(flags)
request.putString(vfs)
request.putHeader(RequestOpen)
}
// EncodePrepare encodes a Prepare request.
func EncodePrepare(request *Message, db uint64, sql string) {
request.putUint64(db)
request.putString(sql)
request.putHeader(RequestPrepare)
}
// EncodeExec encodes a Exec request.
func EncodeExec(request *Message, db uint32, stmt uint32, values NamedValues) {
request.putUint32(db)
request.putUint32(stmt)
request.putNamedValues(values)
request.putHeader(RequestExec)
}
// EncodeQuery encodes a Query request.
func EncodeQuery(request *Message, db uint32, stmt uint32, values NamedValues) {
request.putUint32(db)
request.putUint32(stmt)
request.putNamedValues(values)
request.putHeader(RequestQuery)
}
// EncodeFinalize encodes a Finalize request.
func EncodeFinalize(request *Message, db uint32, stmt uint32) {
request.putUint32(db)
request.putUint32(stmt)
request.putHeader(RequestFinalize)
}
// EncodeExecSQL encodes a ExecSQL request.
func EncodeExecSQL(request *Message, db uint64, sql string, values NamedValues) {
request.putUint64(db)
request.putString(sql)
request.putNamedValues(values)
request.putHeader(RequestExecSQL)
}
// EncodeQuerySQL encodes a QuerySQL request.
func EncodeQuerySQL(request *Message, db uint64, sql string, values NamedValues) {
request.putUint64(db)
request.putString(sql)
request.putNamedValues(values)
request.putHeader(RequestQuerySQL)
}
// EncodeInterrupt encodes a Interrupt request.
func EncodeInterrupt(request *Message, db uint64) {
request.putUint64(db)
request.putHeader(RequestInterrupt)
}
// EncodeJoin encodes a Join request.
func EncodeJoin(request *Message, id uint64, address string) {
request.putUint64(id)
request.putString(address)
request.putHeader(RequestJoin)
}
// EncodePromote encodes a Promote request.
func EncodePromote(request *Message, id uint64) {
request.putUint64(id)
request.putHeader(RequestPromote)
}
// EncodeRemove encodes a Remove request.
func EncodeRemove(request *Message, id uint64) {
request.putUint64(id)
request.putHeader(RequestRemove)
}
// EncodeDump encodes a Dump request.
func EncodeDump(request *Message, name string) {
request.putString(name)
request.putHeader(RequestDump)
}
// EncodeCluster encodes a Cluster request.
func EncodeCluster(request *Message) {
request.putUint64(0)
request.putHeader(RequestCluster)
}

View File

@ -0,0 +1,254 @@
package protocol
// DO NOT EDIT
//
// This file was generated by ./schema.sh
import "fmt"
// DecodeFailure decodes a Failure response.
func DecodeFailure(response *Message) (code uint64, message string, err error) {
mtype, _ := response.getHeader()
if mtype == ResponseFailure {
e := ErrRequest{}
e.Code = response.getUint64()
e.Description = response.getString()
err = e
return
}
if mtype != ResponseFailure {
err = fmt.Errorf("unexpected response type %d", mtype)
return
}
code = response.getUint64()
message = response.getString()
return
}
// DecodeWelcome decodes a Welcome response.
func DecodeWelcome(response *Message) (heartbeatTimeout uint64, err error) {
mtype, _ := response.getHeader()
if mtype == ResponseFailure {
e := ErrRequest{}
e.Code = response.getUint64()
e.Description = response.getString()
err = e
return
}
if mtype != ResponseWelcome {
err = fmt.Errorf("unexpected response type %d", mtype)
return
}
heartbeatTimeout = response.getUint64()
return
}
// DecodeNodeLegacy decodes a NodeLegacy response.
func DecodeNodeLegacy(response *Message) (address string, err error) {
mtype, _ := response.getHeader()
if mtype == ResponseFailure {
e := ErrRequest{}
e.Code = response.getUint64()
e.Description = response.getString()
err = e
return
}
if mtype != ResponseNodeLegacy {
err = fmt.Errorf("unexpected response type %d", mtype)
return
}
address = response.getString()
return
}
// DecodeNode decodes a Node response.
func DecodeNode(response *Message) (id uint64, address string, err error) {
mtype, _ := response.getHeader()
if mtype == ResponseFailure {
e := ErrRequest{}
e.Code = response.getUint64()
e.Description = response.getString()
err = e
return
}
if mtype != ResponseNode {
err = fmt.Errorf("unexpected response type %d", mtype)
return
}
id = response.getUint64()
address = response.getString()
return
}
// DecodeNodes decodes a Nodes response.
func DecodeNodes(response *Message) (servers Nodes, err error) {
mtype, _ := response.getHeader()
if mtype == ResponseFailure {
e := ErrRequest{}
e.Code = response.getUint64()
e.Description = response.getString()
err = e
return
}
if mtype != ResponseNodes {
err = fmt.Errorf("unexpected response type %d", mtype)
return
}
servers = response.getNodes()
return
}
// DecodeDb decodes a Db response.
func DecodeDb(response *Message) (id uint32, err error) {
mtype, _ := response.getHeader()
if mtype == ResponseFailure {
e := ErrRequest{}
e.Code = response.getUint64()
e.Description = response.getString()
err = e
return
}
if mtype != ResponseDb {
err = fmt.Errorf("unexpected response type %d", mtype)
return
}
id = response.getUint32()
response.getUint32()
return
}
// DecodeStmt decodes a Stmt response.
func DecodeStmt(response *Message) (db uint32, id uint32, params uint64, err error) {
mtype, _ := response.getHeader()
if mtype == ResponseFailure {
e := ErrRequest{}
e.Code = response.getUint64()
e.Description = response.getString()
err = e
return
}
if mtype != ResponseStmt {
err = fmt.Errorf("unexpected response type %d", mtype)
return
}
db = response.getUint32()
id = response.getUint32()
params = response.getUint64()
return
}
// DecodeEmpty decodes a Empty response.
func DecodeEmpty(response *Message) (err error) {
mtype, _ := response.getHeader()
if mtype == ResponseFailure {
e := ErrRequest{}
e.Code = response.getUint64()
e.Description = response.getString()
err = e
return
}
if mtype != ResponseEmpty {
err = fmt.Errorf("unexpected response type %d", mtype)
return
}
response.getUint64()
return
}
// DecodeResult decodes a Result response.
func DecodeResult(response *Message) (result Result, err error) {
mtype, _ := response.getHeader()
if mtype == ResponseFailure {
e := ErrRequest{}
e.Code = response.getUint64()
e.Description = response.getString()
err = e
return
}
if mtype != ResponseResult {
err = fmt.Errorf("unexpected response type %d", mtype)
return
}
result = response.getResult()
return
}
// DecodeRows decodes a Rows response.
func DecodeRows(response *Message) (rows Rows, err error) {
mtype, _ := response.getHeader()
if mtype == ResponseFailure {
e := ErrRequest{}
e.Code = response.getUint64()
e.Description = response.getString()
err = e
return
}
if mtype != ResponseRows {
err = fmt.Errorf("unexpected response type %d", mtype)
return
}
rows = response.getRows()
return
}
// DecodeFiles decodes a Files response.
func DecodeFiles(response *Message) (files Files, err error) {
mtype, _ := response.getHeader()
if mtype == ResponseFailure {
e := ErrRequest{}
e.Code = response.getUint64()
e.Description = response.getString()
err = e
return
}
if mtype != ResponseFiles {
err = fmt.Errorf("unexpected response type %d", mtype)
return
}
files = response.getFiles()
return
}

View File

@ -0,0 +1,33 @@
package protocol
//go:generate ./schema.sh --request init
//go:generate ./schema.sh --request Leader unused:uint64
//go:generate ./schema.sh --request Client id:uint64
//go:generate ./schema.sh --request Heartbeat timestamp:uint64
//go:generate ./schema.sh --request Open name:string flags:uint64 vfs:string
//go:generate ./schema.sh --request Prepare db:uint64 sql:string
//go:generate ./schema.sh --request Exec db:uint32 stmt:uint32 values:NamedValues
//go:generate ./schema.sh --request Query db:uint32 stmt:uint32 values:NamedValues
//go:generate ./schema.sh --request Finalize db:uint32 stmt:uint32
//go:generate ./schema.sh --request ExecSQL db:uint64 sql:string values:NamedValues
//go:generate ./schema.sh --request QuerySQL db:uint64 sql:string values:NamedValues
//go:generate ./schema.sh --request Interrupt db:uint64
//go:generate ./schema.sh --request Join id:uint64 address:string
//go:generate ./schema.sh --request Promote id:uint64
//go:generate ./schema.sh --request Remove id:uint64
//go:generate ./schema.sh --request Dump name:string
//go:generate ./schema.sh --request Cluster unused:uint64
//go:generate ./schema.sh --response init
//go:generate ./schema.sh --response Failure code:uint64 message:string
//go:generate ./schema.sh --response Welcome heartbeatTimeout:uint64
//go:generate ./schema.sh --response NodeLegacy address:string
//go:generate ./schema.sh --response Node id:uint64 address:string
//go:generate ./schema.sh --response Nodes servers:Nodes
//go:generate ./schema.sh --response Db id:uint32 unused:uint32
//go:generate ./schema.sh --response Stmt db:uint32 id:uint32 params:uint64
//go:generate ./schema.sh --response Empty unused:uint64
//go:generate ./schema.sh --response Result result:Result
//go:generate ./schema.sh --response Rows rows:Rows
//go:generate ./schema.sh --response Files files:Files

View File

@ -0,0 +1,144 @@
#!/bin/bash
request_init() {
cat > request.go <<EOF
package protocol
// DO NOT EDIT
//
// This file was generated by ./schema.sh
EOF
}
response_init() {
cat > response.go <<EOF
package protocol
// DO NOT EDIT
//
// This file was generated by ./schema.sh
import "fmt"
EOF
}
entity=$1
shift
cmd=$1
shift
if [ "$entity" = "--request" ]; then
if [ "$cmd" = "init" ]; then
request_init
exit
fi
args=""
for i in "${@}"
do
name=$(echo "$i" | cut -f 1 -d :)
type=$(echo "$i" | cut -f 2 -d :)
if [ "$name" = "unused" ]; then
continue
fi
args=$(echo "${args}, ${name} ${type}")
done
cat >> request.go <<EOF
// Encode${cmd} encodes a $cmd request.
func Encode${cmd}(request *Message${args}) {
EOF
for i in "${@}"
do
name=$(echo "$i" | cut -f 1 -d :)
type=$(echo "$i" | cut -f 2 -d :)
if [ "$name" = "unused" ]; then
name=$(echo "0")
fi
cat >> request.go <<EOF
request.put${type^}(${name})
EOF
done
cat >> request.go <<EOF
request.putHeader(Request${cmd})
}
EOF
fi
if [ "$entity" = "--response" ]; then
if [ "$cmd" = "init" ]; then
response_init
exit
fi
returns=""
for i in "${@}"
do
name=$(echo "$i" | cut -f 1 -d :)
type=$(echo "$i" | cut -f 2 -d :)
if [ "$name" = "unused" ]; then
continue
fi
returns=$(echo "${returns}${name} ${type}, ")
done
cat >> response.go <<EOF
// Decode${cmd} decodes a $cmd response.
func Decode${cmd}(response *Message) (${returns}err error) {
mtype, _ := response.getHeader()
if mtype == ResponseFailure {
e := ErrRequest{}
e.Code = response.getUint64()
e.Description = response.getString()
err = e
return
}
if mtype != Response${cmd} {
err = fmt.Errorf("unexpected response type %d", mtype)
return
}
EOF
for i in "${@}"
do
name=$(echo "$i" | cut -f 1 -d :)
type=$(echo "$i" | cut -f 2 -d :)
assign=$(echo "${name} = ")
if [ "$name" = "unused" ]; then
assign=$(echo "")
fi
cat >> response.go <<EOF
${assign}response.get${type^}()
EOF
done
cat >> response.go <<EOF
return
}
EOF
fi

View File

@ -0,0 +1,49 @@
package protocol
import (
"context"
)
// NodeInfo holds information about a single server.
type NodeInfo struct {
ID uint64
Address string
}
// NodeStore is used by a dqlite client to get an initial list of candidate
// dqlite servers that it can dial in order to find a leader server to connect
// to.
//
// Once connected, the client periodically updates the server addresses in the
// store by querying the leader about changes in the cluster (such as servers
// being added or removed).
type NodeStore interface {
// Get return the list of known servers.
Get(context.Context) ([]NodeInfo, error)
// Set updates the list of known cluster servers.
Set(context.Context, []NodeInfo) error
}
// InmemNodeStore keeps the list of servers in memory.
type InmemNodeStore struct {
servers []NodeInfo
}
// NewInmemNodeStore creates NodeStore which stores its data in-memory.
func NewInmemNodeStore() *InmemNodeStore {
return &InmemNodeStore{
servers: make([]NodeInfo, 0),
}
}
// Get the current servers.
func (i *InmemNodeStore) Get(ctx context.Context) ([]NodeInfo, error) {
return i.servers, nil
}
// Set the servers.
func (i *InmemNodeStore) Set(ctx context.Context, servers []NodeInfo) error {
i.servers = servers
return nil
}

126
vendor/github.com/canonical/go-dqlite/node.go generated vendored Normal file
View File

@ -0,0 +1,126 @@
package dqlite
import (
"time"
"github.com/canonical/go-dqlite/client"
"github.com/canonical/go-dqlite/internal/bindings"
"github.com/pkg/errors"
)
// Node runs a dqlite node.
type Node struct {
log client.LogFunc // Logger
server *bindings.Node // Low-level C implementation
acceptCh chan error // Receives connection handling errors
id uint64
address string
bindAddress string
}
// NodeInfo is a convenience alias for client.NodeInfo.
type NodeInfo = client.NodeInfo
// Option can be used to tweak node parameters.
type Option func(*options)
// WithDialFunc sets a custom dial function for the server.
func WithDialFunc(dial client.DialFunc) Option {
return func(options *options) {
options.DialFunc = dial
}
}
// WithBindAddress sets a custom bind address for the server.
func WithBindAddress(address string) Option {
return func(options *options) {
options.BindAddress = address
}
}
// WithNetworkLatency sets the average one-way network latency.
func WithNetworkLatency(latency time.Duration) Option {
return func(options *options) {
options.NetworkLatency = uint64(latency.Nanoseconds())
}
}
// New creates a new Node instance.
func New(id uint64, address string, dir string, options ...Option) (*Node, error) {
o := defaultOptions()
for _, option := range options {
option(o)
}
server, err := bindings.NewNode(id, address, dir)
if err != nil {
return nil, err
}
if o.DialFunc != nil {
if err := server.SetDialFunc(o.DialFunc); err != nil {
return nil, err
}
}
if o.BindAddress != "" {
if err := server.SetBindAddress(o.BindAddress); err != nil {
return nil, err
}
}
if o.NetworkLatency != 0 {
if err := server.SetNetworkLatency(o.NetworkLatency); err != nil {
return nil, err
}
}
s := &Node{
server: server,
acceptCh: make(chan error, 1),
id: id,
address: address,
bindAddress: o.BindAddress,
}
return s, nil
}
// BindAddress returns the network address the node is listening to.
func (s *Node) BindAddress() string {
return s.server.GetBindAddress()
}
// Start serving requests.
func (s *Node) Start() error {
return s.server.Start()
}
// Recover a node by forcing a new cluster configuration.
func (s *Node) Recover(cluster []NodeInfo) error {
return s.server.Recover(cluster)
}
// Hold configuration options for a dqlite server.
type options struct {
Log client.LogFunc
DialFunc client.DialFunc
BindAddress string
NetworkLatency uint64
}
// Close the server, releasing all resources it created.
func (s *Node) Close() error {
// Send a stop signal to the dqlite event loop.
if err := s.server.Stop(); err != nil {
return errors.Wrap(err, "server failed to stop")
}
s.server.Close()
return nil
}
// Create a options object with sane defaults.
func defaultOptions() *options {
return &options{
DialFunc: client.DefaultDialFunc,
}
}

View File

@ -32,7 +32,7 @@ import (
func init() {
initHijack()
// Go `log` pacakge uses os.Stderr.
// Go `log` package uses os.Stderr.
SetFormatter(NewDefaultFormatter(os.Stderr))
SetGlobalLogLevel(INFO)
}

1
vendor/github.com/flosch/pongo2/.gitattributes generated vendored Normal file
View File

@ -0,0 +1 @@
* text eol=lf

42
vendor/github.com/flosch/pongo2/.gitignore generated vendored Normal file
View File

@ -0,0 +1,42 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
.idea
.vscode
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
.project
EBNF.txt
test1.tpl
pongo2_internal_test.go
tpl-error.out
/count.out
/cover.out
*.swp
*.iml
/cpu.out
/mem.out
/pongo2.test
*.error
/profile
/coverage.out
/pongo2_internal_test.ignore
go.sum

8
vendor/github.com/flosch/pongo2/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,8 @@
language: go
os:
- linux
- osx
go:
- 1.12
script:
- go test -v

11
vendor/github.com/flosch/pongo2/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,11 @@
Main author and maintainer of pongo2:
* Florian Schlachter <flori@n-schlachter.de>
Contributors (in no specific order):
* @romanoaugusto88
* @vitalbh
* @blaubaer
Feel free to add yourself to the list or to modify your entry if you did a contribution.

20
vendor/github.com/flosch/pongo2/LICENSE generated vendored Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013-2014 Florian Schlachter
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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

@ -0,0 +1,273 @@
# [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)
[![Build Status](https://travis-ci.org/flosch/pongo2.svg?branch=master)](https://travis-ci.org/flosch/pongo2)
[![Backers on Open Collective](https://opencollective.com/pongo2/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/pongo2/sponsors/badge.svg)](#sponsors)
pongo2 is the successor of [pongo](https://github.com/flosch/pongo), a Django-syntax like templating-language.
Install/update using `go get` (no dependencies required by pongo2):
```
go get -u github.com/flosch/pongo2
```
Please use the [issue tracker](https://github.com/flosch/pongo2/issues) if you're encountering any problems with pongo2 or if you need help with implementing tags or filters ([create a ticket!](https://github.com/flosch/pongo2/issues/new)).
## First impression of a template
```HTML+Django
<html><head><title>Our admins and users</title></head>
{# This is a short example to give you a quick overview of pongo2's syntax. #}
{% macro user_details(user, is_admin=false) %}
<div class="user_item">
<!-- Let's indicate a user's good karma -->
<h2 {% if (user.karma >= 40) || (user.karma > calc_avg_karma(userlist)+5) %}
class="karma-good"{% endif %}>
<!-- This will call user.String() automatically if available: -->
{{ user }}
</h2>
<!-- Will print a human-readable time duration like "3 weeks ago" -->
<p>This user registered {{ user.register_date|naturaltime }}.</p>
<!-- Let's allow the users to write down their biography using markdown;
we will only show the first 15 words as a preview -->
<p>The user's biography:</p>
<p>{{ user.biography|markdown|truncatewords_html:15 }}
<a href="/user/{{ user.id }}/">read more</a></p>
{% if is_admin %}<p>This user is an admin!</p>{% endif %}
</div>
{% endmacro %}
<body>
<!-- Make use of the macro defined above to avoid repetitive HTML code
since we want to use the same code for admins AND members -->
<h1>Our admins</h1>
{% for admin in adminlist %}
{{ user_details(admin, true) }}
{% endfor %}
<h1>Our members</h1>
{% for user in userlist %}
{{ user_details(user) }}
{% endfor %}
</body>
</html>
```
## Development status
**Latest stable release**: v3.0 (`go get -u gopkg.in/flosch/pongo2.v3` / [`v3`](https://github.com/flosch/pongo2/tree/v3)-branch)
**Current development**: v4 (`master`-branch)
*Note*: With the release of pongo v4 the branch v2 will be deprecated.
**Deprecated versions** (not supported anymore): v1
| Topic | Status |
| ------------------------------------ | -------------------------------------------------------------------------------------- |
| Django version compatibility: | [1.7](https://docs.djangoproject.com/en/1.7/ref/templates/builtins/) |
| *Missing* (planned) **filters**: | none ([hints](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3)) |
| *Missing* (planned) **tags**: | none ([hints](https://github.com/flosch/pongo2/blob/master/tags.go#L3)) |
Please also have a look on the [caveats](https://github.com/flosch/pongo2#caveats) and on the [official add-ons](https://github.com/flosch/pongo2#official).
## Features (and new in pongo2)
* Entirely rewritten from the ground-up.
* [Advanced C-like expressions](https://github.com/flosch/pongo2/blob/master/template_tests/expressions.tpl).
* [Complex function calls within expressions](https://github.com/flosch/pongo2/blob/master/template_tests/function_calls_wrapper.tpl).
* [Easy API to create new filters and tags](http://godoc.org/github.com/flosch/pongo2#RegisterFilter) ([including parsing arguments](http://godoc.org/github.com/flosch/pongo2#Parser))
* Additional features:
* Macros including importing macros from other files (see [template_tests/macro.tpl](https://github.com/flosch/pongo2/blob/master/template_tests/macro.tpl))
* [Template sandboxing](https://godoc.org/github.com/flosch/pongo2#TemplateSet) ([directory patterns](http://golang.org/pkg/path/filepath/#Match), banned tags/filters)
## Recent API changes within pongo2
If you're using the `master`-branch of pongo2, you might be interested in this section. Since pongo2 is still in development (even though there is a first stable release!), there could be (backwards-incompatible) API changes over time. To keep track of these and therefore make it painless for you to adapt your codebase, I'll list them here.
* Function signature for tag execution changed: not taking a `bytes.Buffer` anymore; instead `Execute()`-functions are now taking a `TemplateWriter` interface.
* Function signature for tag and filter parsing/execution changed (`error` return type changed to `*Error`).
* `INodeEvaluator` has been removed and got replaced by `IEvaluator`. You can change your existing tags/filters by simply replacing the interface.
* Two new helper functions: [`RenderTemplateFile()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateFile) and [`RenderTemplateString()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateString).
* `Template.ExecuteRW()` is now [`Template.ExecuteWriter()`](https://godoc.org/github.com/flosch/pongo2#Template.ExecuteWriter)
* `Template.Execute*()` functions do now take a `pongo2.Context` directly (no pointer anymore).
## How you can help
* Write [filters](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3) / [tags] by forking pongo2 and sending pull requests
* Write/improve code tests (use the following command to see what tests are missing: `go test -v -cover -covermode=count -coverprofile=cover.out && go tool cover -html=cover.out` or have a look on [gocover.io/github.com/flosch/pongo2](http://gocover.io/github.com/flosch/pongo2))
* Write/improve template tests (see the `template_tests/` directory)
* Write middleware, libraries and websites using pongo2. :-)
# Documentation
For a documentation on how the templating language works you can [head over to the Django documentation](https://docs.djangoproject.com/en/dev/topics/templates/). pongo2 aims to be compatible with it.
You can access pongo2's API documentation on [godoc](https://godoc.org/github.com/flosch/pongo2).
## Caveats
### Filters
* **date** / **time**: The `date` and `time` filter are taking the Golang specific time- and date-format (not Django's one) currently. [Take a look on the format here](http://golang.org/pkg/time/#Time.Format).
* **stringformat**: `stringformat` does **not** take Python's string format syntax as a parameter, instead it takes Go's. Essentially `{{ 3.14|stringformat:"pi is %.2f" }}` is `fmt.Sprintf("pi is %.2f", 3.14)`.
* **escape** / **force_escape**: Unlike Django's behaviour, the `escape`-filter is applied immediately. Therefore there is no need for a `force_escape`-filter yet.
### Tags
* **for**: All the `forloop` fields (like `forloop.counter`) are written with a capital letter at the beginning. For example, the `counter` can be accessed by `forloop.Counter` and the parentloop by `forloop.Parentloop`.
* **now**: takes Go's time format (see **date** and **time**-filter).
### Misc
* **not in-operator**: You can check whether a map/struct/string contains a key/field/substring by using the in-operator (or the negation of it):
`{% if key in map %}Key is in map{% else %}Key not in map{% endif %}` or `{% if !(key in map) %}Key is NOT in map{% else %}Key is in map{% endif %}`.
# Add-ons, libraries and helpers
## Official
* [ponginae](https://github.com/flosch/ponginae) - A web-framework for Go (using pongo2).
* [pongo2-tools](https://github.com/flosch/pongo2-tools) - Official tools and helpers for pongo2
* [pongo2-addons](https://github.com/flosch/pongo2-addons) - Official additional filters/tags for pongo2 (for example a **markdown**-filter). They are in their own repository because they're relying on 3rd-party-libraries.
## 3rd-party
* [beego-pongo2](https://github.com/oal/beego-pongo2) - A tiny little helper for using Pongo2 with [Beego](https://github.com/astaxie/beego).
* [beego-pongo2.v2](https://github.com/ipfans/beego-pongo2.v2) - Same as `beego-pongo2`, but for pongo2 v2.
* [macaron-pongo2](https://github.com/macaron-contrib/pongo2) - pongo2 support for [Macaron](https://github.com/Unknwon/macaron), a modular web framework.
* [ginpongo2](https://github.com/ngerakines/ginpongo2) - middleware for [gin](github.com/gin-gonic/gin) to use pongo2 templates
* [Build'n support for Iris' template engine](https://github.com/kataras/iris)
* [pongo2gin](https://gitlab.com/go-box/pongo2gin) - alternative renderer for [gin](github.com/gin-gonic/gin) to use pongo2 templates
* [pongo2-trans](https://github.com/digitalcrab/pongo2trans) - `trans`-tag implementation for internationalization
* [tpongo2](https://github.com/tango-contrib/tpongo2) - pongo2 support for [Tango](https://github.com/lunny/tango), a micro-kernel & pluggable web framework.
* [p2cli](https://github.com/wrouesnel/p2cli) - command line templating utility based on pongo2
Please add your project to this list and send me a pull request when you've developed something nice for pongo2.
# API-usage examples
Please see the documentation for a full list of provided API methods.
## A tiny example (template string)
```Go
// Compile the template first (i. e. creating the AST)
tpl, err := pongo2.FromString("Hello {{ name|capfirst }}!")
if err != nil {
panic(err)
}
// Now you can render the template with the given
// pongo2.Context how often you want to.
out, err := tpl.Execute(pongo2.Context{"name": "florian"})
if err != nil {
panic(err)
}
fmt.Println(out) // Output: Hello Florian!
```
## Example server-usage (template file)
```Go
package main
import (
"github.com/flosch/pongo2"
"net/http"
)
// Pre-compiling the templates at application startup using the
// little Must()-helper function (Must() will panic if FromFile()
// or FromString() will return with an error - that's it).
// It's faster to pre-compile it anywhere at startup and only
// execute the template later.
var tplExample = pongo2.Must(pongo2.FromFile("example.html"))
func examplePage(w http.ResponseWriter, r *http.Request) {
// Execute the template per HTTP request
err := tplExample.ExecuteWriter(pongo2.Context{"query": r.FormValue("query")}, w)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func main() {
http.HandleFunc("/", examplePage)
http.ListenAndServe(":8080", nil)
}
```
# Benchmark
The benchmarks have been run on the my machine (`Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz`) using the command:
go test -bench . -cpu 1,2,4,8
All benchmarks are compiling (depends on the benchmark) and executing the `template_tests/complex.tpl` template.
The results are:
BenchmarkExecuteComplexWithSandboxActive 50000 60450 ns/op
BenchmarkExecuteComplexWithSandboxActive-2 50000 56998 ns/op
BenchmarkExecuteComplexWithSandboxActive-4 50000 60343 ns/op
BenchmarkExecuteComplexWithSandboxActive-8 50000 64229 ns/op
BenchmarkCompileAndExecuteComplexWithSandboxActive 10000 164410 ns/op
BenchmarkCompileAndExecuteComplexWithSandboxActive-2 10000 156682 ns/op
BenchmarkCompileAndExecuteComplexWithSandboxActive-4 10000 164821 ns/op
BenchmarkCompileAndExecuteComplexWithSandboxActive-8 10000 171806 ns/op
BenchmarkParallelExecuteComplexWithSandboxActive 50000 60428 ns/op
BenchmarkParallelExecuteComplexWithSandboxActive-2 50000 31887 ns/op
BenchmarkParallelExecuteComplexWithSandboxActive-4 100000 22810 ns/op
BenchmarkParallelExecuteComplexWithSandboxActive-8 100000 18820 ns/op
BenchmarkExecuteComplexWithoutSandbox 50000 56942 ns/op
BenchmarkExecuteComplexWithoutSandbox-2 50000 56168 ns/op
BenchmarkExecuteComplexWithoutSandbox-4 50000 57838 ns/op
BenchmarkExecuteComplexWithoutSandbox-8 50000 60539 ns/op
BenchmarkCompileAndExecuteComplexWithoutSandbox 10000 162086 ns/op
BenchmarkCompileAndExecuteComplexWithoutSandbox-2 10000 159771 ns/op
BenchmarkCompileAndExecuteComplexWithoutSandbox-4 10000 163826 ns/op
BenchmarkCompileAndExecuteComplexWithoutSandbox-8 10000 169062 ns/op
BenchmarkParallelExecuteComplexWithoutSandbox 50000 57152 ns/op
BenchmarkParallelExecuteComplexWithoutSandbox-2 50000 30276 ns/op
BenchmarkParallelExecuteComplexWithoutSandbox-4 100000 22065 ns/op
BenchmarkParallelExecuteComplexWithoutSandbox-8 100000 18034 ns/op
Benchmarked on October 2nd 2014.
## Contributors
This project exists thanks to all the people who contribute.
<a href="https://github.com/flosch/pongo2/graphs/contributors"><img src="https://opencollective.com/pongo2/contributors.svg?width=890&button=false" /></a>
## Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/pongo2#backer)]
<a href="https://opencollective.com/pongo2#backers" target="_blank"><img src="https://opencollective.com/pongo2/backers.svg?width=890"></a>
## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/pongo2#sponsor)]
<a href="https://opencollective.com/pongo2/sponsor/0/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/pongo2/sponsor/1/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/pongo2/sponsor/2/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/pongo2/sponsor/3/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/pongo2/sponsor/4/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/pongo2/sponsor/5/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/pongo2/sponsor/6/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/pongo2/sponsor/7/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/pongo2/sponsor/8/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/pongo2/sponsor/9/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/9/avatar.svg"></a>

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

@ -0,0 +1,136 @@
package pongo2
import (
"regexp"
"github.com/juju/errors"
)
var reIdentifiers = regexp.MustCompile("^[a-zA-Z0-9_]+$")
var autoescape = true
func SetAutoescape(newValue bool) {
autoescape = newValue
}
// A Context type provides constants, variables, instances or functions to a template.
//
// pongo2 automatically provides meta-information or functions through the "pongo2"-key.
// Currently, context["pongo2"] contains the following keys:
// 1. version: returns the version string
//
// Template examples for accessing items from your context:
// {{ myconstant }}
// {{ myfunc("test", 42) }}
// {{ user.name }}
// {{ pongo2.version }}
type Context map[string]interface{}
func (c Context) checkForValidIdentifiers() *Error {
for k, v := range c {
if !reIdentifiers.MatchString(k) {
return &Error{
Sender: "checkForValidIdentifiers",
OrigError: errors.Errorf("context-key '%s' (value: '%+v') is not a valid identifier", k, v),
}
}
}
return nil
}
// Update updates this context with the key/value-pairs from another context.
func (c Context) Update(other Context) Context {
for k, v := range other {
c[k] = v
}
return c
}
// ExecutionContext contains all data important for the current rendering state.
//
// If you're writing a custom tag, your tag's Execute()-function will
// have access to the ExecutionContext. This struct stores anything
// about the current rendering process's Context including
// the Context provided by the user (field Public).
// You can safely use the Private context to provide data to the user's
// template (like a 'forloop'-information). The Shared-context is used
// to share data between tags. All ExecutionContexts share this context.
//
// Please be careful when accessing the Public data.
// PLEASE DO NOT MODIFY THE PUBLIC CONTEXT (read-only).
//
// To create your own execution context within tags, use the
// NewChildExecutionContext(parent) function.
type ExecutionContext struct {
template *Template
Autoescape bool
Public Context
Private Context
Shared Context
}
var pongo2MetaContext = Context{
"version": Version,
}
func newExecutionContext(tpl *Template, ctx Context) *ExecutionContext {
privateCtx := make(Context)
// Make the pongo2-related funcs/vars available to the context
privateCtx["pongo2"] = pongo2MetaContext
return &ExecutionContext{
template: tpl,
Public: ctx,
Private: privateCtx,
Autoescape: autoescape,
}
}
func NewChildExecutionContext(parent *ExecutionContext) *ExecutionContext {
newctx := &ExecutionContext{
template: parent.template,
Public: parent.Public,
Private: make(Context),
Autoescape: parent.Autoescape,
}
newctx.Shared = parent.Shared
// Copy all existing private items
newctx.Private.Update(parent.Private)
return newctx
}
func (ctx *ExecutionContext) Error(msg string, token *Token) *Error {
return ctx.OrigError(errors.New(msg), token)
}
func (ctx *ExecutionContext) OrigError(err error, token *Token) *Error {
filename := ctx.template.name
var line, col int
if token != nil {
// No tokens available
// TODO: Add location (from where?)
filename = token.Filename
line = token.Line
col = token.Col
}
return &Error{
Template: ctx.template,
Filename: filename,
Line: line,
Column: col,
Token: token,
Sender: "execution",
OrigError: err,
}
}
func (ctx *ExecutionContext) Logf(format string, args ...interface{}) {
ctx.template.set.logf(format, args...)
}

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

@ -0,0 +1,31 @@
// A Django-syntax like template-engine
//
// Blog posts about pongo2 (including introduction and migration):
// https://www.florian-schlachter.de/?tag=pongo2
//
// Complete documentation on the template language:
// https://docs.djangoproject.com/en/dev/topics/templates/
//
// Try out pongo2 live in the pongo2 playground:
// https://www.florian-schlachter.de/pongo2/
//
// Make sure to read README.md in the repository as well.
//
// A tiny example with template strings:
//
// (Snippet on playground: https://www.florian-schlachter.de/pongo2/?id=1206546277)
//
// // Compile the template first (i. e. creating the AST)
// tpl, err := pongo2.FromString("Hello {{ name|capfirst }}!")
// if err != nil {
// panic(err)
// }
// // Now you can render the template with the given
// // pongo2.Context how often you want to.
// out, err := tpl.Execute(pongo2.Context{"name": "fred"})
// if err != nil {
// panic(err)
// }
// fmt.Println(out) // Output: Hello Fred!
//
package pongo2

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

@ -0,0 +1,91 @@
package pongo2
import (
"bufio"
"fmt"
"os"
)
// The Error type is being used to address an error during lexing, parsing or
// execution. If you want to return an error object (for example in your own
// tag or filter) fill this object with as much information as you have.
// Make sure "Sender" is always given (if you're returning an error within
// a filter, make Sender equals 'filter:yourfilter'; same goes for tags: 'tag:mytag').
// It's okay if you only fill in ErrorMsg if you don't have any other details at hand.
type Error struct {
Template *Template
Filename string
Line int
Column int
Token *Token
Sender string
OrigError error
}
func (e *Error) updateFromTokenIfNeeded(template *Template, t *Token) *Error {
if e.Template == nil {
e.Template = template
}
if e.Token == nil {
e.Token = t
if e.Line <= 0 {
e.Line = t.Line
e.Column = t.Col
}
}
return e
}
// Returns a nice formatted error string.
func (e *Error) Error() string {
s := "[Error"
if e.Sender != "" {
s += " (where: " + e.Sender + ")"
}
if e.Filename != "" {
s += " in " + e.Filename
}
if e.Line > 0 {
s += fmt.Sprintf(" | Line %d Col %d", e.Line, e.Column)
if e.Token != nil {
s += fmt.Sprintf(" near '%s'", e.Token.Val)
}
}
s += "] "
s += e.OrigError.Error()
return s
}
// RawLine returns the affected line from the original template, if available.
func (e *Error) RawLine() (line string, available bool, outErr error) {
if e.Line <= 0 || e.Filename == "<string>" {
return "", false, nil
}
filename := e.Filename
if e.Template != nil {
filename = e.Template.set.resolveFilename(e.Template, e.Filename)
}
file, err := os.Open(filename)
if err != nil {
return "", false, err
}
defer func() {
err := file.Close()
if err != nil && outErr == nil {
outErr = err
}
}()
scanner := bufio.NewScanner(file)
l := 0
for scanner.Scan() {
l++
if l == e.Line {
return scanner.Text(), true, nil
}
}
return "", false, nil
}

143
vendor/github.com/flosch/pongo2/filters.go generated vendored Normal file
View File

@ -0,0 +1,143 @@
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
func init() {
filters = make(map[string]FilterFunction)
}
// FilterExists returns true if the given filter is already registered
func FilterExists(name string) bool {
_, existing := filters[name]
return existing
}
// RegisterFilter registers a new filter. If there's already a filter with the same
// name, RegisterFilter will panic. You usually want to call this
// function in the filter's init() function:
// http://golang.org/doc/effective_go.html#init
//
// See http://www.florian-schlachter.de/post/pongo2/ for more about
// writing filters and tags.
func RegisterFilter(name string, fn FilterFunction) error {
if FilterExists(name) {
return errors.Errorf("filter with name '%s' is already registered", name)
}
filters[name] = fn
return nil
}
// ReplaceFilter replaces an already registered filter with a new implementation. Use this
// function with caution since it allows you to change existing filter behaviour.
func ReplaceFilter(name string, fn FilterFunction) error {
if !FilterExists(name) {
return errors.Errorf("filter with name '%s' does not exist (therefore cannot be overridden)", name)
}
filters[name] = fn
return nil
}
// MustApplyFilter behaves like ApplyFilter, but panics on an error.
func MustApplyFilter(name string, value *Value, param *Value) *Value {
val, err := ApplyFilter(name, value, param)
if err != nil {
panic(err)
}
return val
}
// ApplyFilter applies a filter to a given value using the given parameters.
// Returns a *pongo2.Value or an error.
func ApplyFilter(name string, value *Value, param *Value) (*Value, *Error) {
fn, existing := filters[name]
if !existing {
return nil, &Error{
Sender: "applyfilter",
OrigError: errors.Errorf("Filter with name '%s' not found.", name),
}
}
// Make sure param is a *Value
if param == nil {
param = AsValue(nil)
}
return fn(value, param)
}
type filterCall struct {
token *Token
name string
parameter IEvaluator
filterFunc FilterFunction
}
func (fc *filterCall) Execute(v *Value, ctx *ExecutionContext) (*Value, *Error) {
var param *Value
var err *Error
if fc.parameter != nil {
param, err = fc.parameter.Evaluate(ctx)
if err != nil {
return nil, err
}
} else {
param = AsValue(nil)
}
filteredValue, err := fc.filterFunc(v, param)
if err != nil {
return nil, err.updateFromTokenIfNeeded(ctx.template, fc.token)
}
return filteredValue, nil
}
// Filter = IDENT | IDENT ":" FilterArg | IDENT "|" Filter
func (p *Parser) parseFilter() (*filterCall, *Error) {
identToken := p.MatchType(TokenIdentifier)
// Check filter ident
if identToken == nil {
return nil, p.Error("Filter name must be an identifier.", nil)
}
filter := &filterCall{
token: identToken,
name: identToken.Val,
}
// Get the appropriate filter function and bind it
filterFn, exists := filters[identToken.Val]
if !exists {
return nil, p.Error(fmt.Sprintf("Filter '%s' does not exist.", identToken.Val), identToken)
}
filter.filterFunc = filterFn
// Check for filter-argument (2 tokens needed: ':' ARG)
if p.Match(TokenSymbol, ":") != nil {
if p.Peek(TokenSymbol, "}}") != nil {
return nil, p.Error("Filter parameter required after ':'.", nil)
}
// Get filter argument expression
v, err := p.parseVariableOrLiteral()
if err != nil {
return nil, err
}
filter.parameter = v
}
return filter, nil
}

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

@ -0,0 +1,927 @@
package pongo2
/* Filters that are provided through github.com/flosch/pongo2-addons:
------------------------------------------------------------------
filesizeformat
slugify
timesince
timeuntil
Filters that won't be added:
----------------------------
get_static_prefix (reason: web-framework specific)
pprint (reason: python-specific)
static (reason: web-framework specific)
Reconsideration (not implemented yet):
--------------------------------------
force_escape (reason: not yet needed since this is the behaviour of pongo2's escape filter)
safeseq (reason: same reason as `force_escape`)
unordered_list (python-specific; not sure whether needed or not)
dictsort (python-specific; maybe one could add a filter to sort a list of structs by a specific field name)
dictsortreversed (see dictsort)
*/
import (
"bytes"
"fmt"
"math/rand"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/juju/errors"
)
func init() {
rand.Seed(time.Now().Unix())
RegisterFilter("escape", filterEscape)
RegisterFilter("safe", filterSafe)
RegisterFilter("escapejs", filterEscapejs)
RegisterFilter("add", filterAdd)
RegisterFilter("addslashes", filterAddslashes)
RegisterFilter("capfirst", filterCapfirst)
RegisterFilter("center", filterCenter)
RegisterFilter("cut", filterCut)
RegisterFilter("date", filterDate)
RegisterFilter("default", filterDefault)
RegisterFilter("default_if_none", filterDefaultIfNone)
RegisterFilter("divisibleby", filterDivisibleby)
RegisterFilter("first", filterFirst)
RegisterFilter("floatformat", filterFloatformat)
RegisterFilter("get_digit", filterGetdigit)
RegisterFilter("iriencode", filterIriencode)
RegisterFilter("join", filterJoin)
RegisterFilter("last", filterLast)
RegisterFilter("length", filterLength)
RegisterFilter("length_is", filterLengthis)
RegisterFilter("linebreaks", filterLinebreaks)
RegisterFilter("linebreaksbr", filterLinebreaksbr)
RegisterFilter("linenumbers", filterLinenumbers)
RegisterFilter("ljust", filterLjust)
RegisterFilter("lower", filterLower)
RegisterFilter("make_list", filterMakelist)
RegisterFilter("phone2numeric", filterPhone2numeric)
RegisterFilter("pluralize", filterPluralize)
RegisterFilter("random", filterRandom)
RegisterFilter("removetags", filterRemovetags)
RegisterFilter("rjust", filterRjust)
RegisterFilter("slice", filterSlice)
RegisterFilter("split", filterSplit)
RegisterFilter("stringformat", filterStringformat)
RegisterFilter("striptags", filterStriptags)
RegisterFilter("time", filterDate) // time uses filterDate (same golang-format)
RegisterFilter("title", filterTitle)
RegisterFilter("truncatechars", filterTruncatechars)
RegisterFilter("truncatechars_html", filterTruncatecharsHTML)
RegisterFilter("truncatewords", filterTruncatewords)
RegisterFilter("truncatewords_html", filterTruncatewordsHTML)
RegisterFilter("upper", filterUpper)
RegisterFilter("urlencode", filterUrlencode)
RegisterFilter("urlize", filterUrlize)
RegisterFilter("urlizetrunc", filterUrlizetrunc)
RegisterFilter("wordcount", filterWordcount)
RegisterFilter("wordwrap", filterWordwrap)
RegisterFilter("yesno", filterYesno)
RegisterFilter("float", filterFloat) // pongo-specific
RegisterFilter("integer", filterInteger) // pongo-specific
}
func filterTruncatecharsHelper(s string, newLen int) string {
runes := []rune(s)
if newLen < len(runes) {
if newLen >= 3 {
return fmt.Sprintf("%s...", string(runes[:newLen-3]))
}
// Not enough space for the ellipsis
return string(runes[:newLen])
}
return string(runes)
}
func filterTruncateHTMLHelper(value string, newOutput *bytes.Buffer, cond func() bool, fn func(c rune, s int, idx int) int, finalize func()) {
vLen := len(value)
var tagStack []string
idx := 0
for idx < vLen && !cond() {
c, s := utf8.DecodeRuneInString(value[idx:])
if c == utf8.RuneError {
idx += s
continue
}
if c == '<' {
newOutput.WriteRune(c)
idx += s // consume "<"
if idx+1 < vLen {
if value[idx] == '/' {
// Close tag
newOutput.WriteString("/")
tag := ""
idx++ // consume "/"
for idx < vLen {
c2, size2 := utf8.DecodeRuneInString(value[idx:])
if c2 == utf8.RuneError {
idx += size2
continue
}
// End of tag found
if c2 == '>' {
idx++ // consume ">"
break
}
tag += string(c2)
idx += size2
}
if len(tagStack) > 0 {
// Ideally, the close tag is TOP of tag stack
// In malformed HTML, it must not be, so iterate through the stack and remove the tag
for i := len(tagStack) - 1; i >= 0; i-- {
if tagStack[i] == tag {
// Found the tag
tagStack[i] = tagStack[len(tagStack)-1]
tagStack = tagStack[:len(tagStack)-1]
break
}
}
}
newOutput.WriteString(tag)
newOutput.WriteString(">")
} else {
// Open tag
tag := ""
params := false
for idx < vLen {
c2, size2 := utf8.DecodeRuneInString(value[idx:])
if c2 == utf8.RuneError {
idx += size2
continue
}
newOutput.WriteRune(c2)
// End of tag found
if c2 == '>' {
idx++ // consume ">"
break
}
if !params {
if c2 == ' ' {
params = true
} else {
tag += string(c2)
}
}
idx += size2
}
// Add tag to stack
tagStack = append(tagStack, tag)
}
}
} else {
idx = fn(c, s, idx)
}
}
finalize()
for i := len(tagStack) - 1; i >= 0; i-- {
tag := tagStack[i]
// Close everything from the regular tag stack
newOutput.WriteString(fmt.Sprintf("</%s>", tag))
}
}
func filterTruncatechars(in *Value, param *Value) (*Value, *Error) {
s := in.String()
newLen := param.Integer()
return AsValue(filterTruncatecharsHelper(s, newLen)), nil
}
func filterTruncatecharsHTML(in *Value, param *Value) (*Value, *Error) {
value := in.String()
newLen := max(param.Integer()-3, 0)
newOutput := bytes.NewBuffer(nil)
textcounter := 0
filterTruncateHTMLHelper(value, newOutput, func() bool {
return textcounter >= newLen
}, func(c rune, s int, idx int) int {
textcounter++
newOutput.WriteRune(c)
return idx + s
}, func() {
if textcounter >= newLen && textcounter < len(value) {
newOutput.WriteString("...")
}
})
return AsSafeValue(newOutput.String()), nil
}
func filterTruncatewords(in *Value, param *Value) (*Value, *Error) {
words := strings.Fields(in.String())
n := param.Integer()
if n <= 0 {
return AsValue(""), nil
}
nlen := min(len(words), n)
out := make([]string, 0, nlen)
for i := 0; i < nlen; i++ {
out = append(out, words[i])
}
if n < len(words) {
out = append(out, "...")
}
return AsValue(strings.Join(out, " ")), nil
}
func filterTruncatewordsHTML(in *Value, param *Value) (*Value, *Error) {
value := in.String()
newLen := max(param.Integer(), 0)
newOutput := bytes.NewBuffer(nil)
wordcounter := 0
filterTruncateHTMLHelper(value, newOutput, func() bool {
return wordcounter >= newLen
}, func(_ rune, _ int, idx int) int {
// Get next word
wordFound := false
for idx < len(value) {
c2, size2 := utf8.DecodeRuneInString(value[idx:])
if c2 == utf8.RuneError {
idx += size2
continue
}
if c2 == '<' {
// HTML tag start, don't consume it
return idx
}
newOutput.WriteRune(c2)
idx += size2
if c2 == ' ' || c2 == '.' || c2 == ',' || c2 == ';' {
// Word ends here, stop capturing it now
break
} else {
wordFound = true
}
}
if wordFound {
wordcounter++
}
return idx
}, func() {
if wordcounter >= newLen {
newOutput.WriteString("...")
}
})
return AsSafeValue(newOutput.String()), nil
}
func filterEscape(in *Value, param *Value) (*Value, *Error) {
output := strings.Replace(in.String(), "&", "&amp;", -1)
output = strings.Replace(output, ">", "&gt;", -1)
output = strings.Replace(output, "<", "&lt;", -1)
output = strings.Replace(output, "\"", "&quot;", -1)
output = strings.Replace(output, "'", "&#39;", -1)
return AsValue(output), nil
}
func filterSafe(in *Value, param *Value) (*Value, *Error) {
return in, nil // nothing to do here, just to keep track of the safe application
}
func filterEscapejs(in *Value, param *Value) (*Value, *Error) {
sin := in.String()
var b bytes.Buffer
idx := 0
for idx < len(sin) {
c, size := utf8.DecodeRuneInString(sin[idx:])
if c == utf8.RuneError {
idx += size
continue
}
if c == '\\' {
// Escape seq?
if idx+1 < len(sin) {
switch sin[idx+1] {
case 'r':
b.WriteString(fmt.Sprintf(`\u%04X`, '\r'))
idx += 2
continue
case 'n':
b.WriteString(fmt.Sprintf(`\u%04X`, '\n'))
idx += 2
continue
/*case '\'':
b.WriteString(fmt.Sprintf(`\u%04X`, '\''))
idx += 2
continue
case '"':
b.WriteString(fmt.Sprintf(`\u%04X`, '"'))
idx += 2
continue*/
}
}
}
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == ' ' || c == '/' {
b.WriteRune(c)
} else {
b.WriteString(fmt.Sprintf(`\u%04X`, c))
}
idx += size
}
return AsValue(b.String()), nil
}
func filterAdd(in *Value, param *Value) (*Value, *Error) {
if in.IsNumber() && param.IsNumber() {
if in.IsFloat() || param.IsFloat() {
return AsValue(in.Float() + param.Float()), nil
}
return AsValue(in.Integer() + param.Integer()), nil
}
// If in/param is not a number, we're relying on the
// Value's String() conversion and just add them both together
return AsValue(in.String() + param.String()), nil
}
func filterAddslashes(in *Value, param *Value) (*Value, *Error) {
output := strings.Replace(in.String(), "\\", "\\\\", -1)
output = strings.Replace(output, "\"", "\\\"", -1)
output = strings.Replace(output, "'", "\\'", -1)
return AsValue(output), nil
}
func filterCut(in *Value, param *Value) (*Value, *Error) {
return AsValue(strings.Replace(in.String(), param.String(), "", -1)), nil
}
func filterLength(in *Value, param *Value) (*Value, *Error) {
return AsValue(in.Len()), nil
}
func filterLengthis(in *Value, param *Value) (*Value, *Error) {
return AsValue(in.Len() == param.Integer()), nil
}
func filterDefault(in *Value, param *Value) (*Value, *Error) {
if !in.IsTrue() {
return param, nil
}
return in, nil
}
func filterDefaultIfNone(in *Value, param *Value) (*Value, *Error) {
if in.IsNil() {
return param, nil
}
return in, nil
}
func filterDivisibleby(in *Value, param *Value) (*Value, *Error) {
if param.Integer() == 0 {
return AsValue(false), nil
}
return AsValue(in.Integer()%param.Integer() == 0), nil
}
func filterFirst(in *Value, param *Value) (*Value, *Error) {
if in.CanSlice() && in.Len() > 0 {
return in.Index(0), nil
}
return AsValue(""), nil
}
func filterFloatformat(in *Value, param *Value) (*Value, *Error) {
val := in.Float()
decimals := -1
if !param.IsNil() {
// Any argument provided?
decimals = param.Integer()
}
// if the argument is not a number (e. g. empty), the default
// behaviour is trim the result
trim := !param.IsNumber()
if decimals <= 0 {
// argument is negative or zero, so we
// want the output being trimmed
decimals = -decimals
trim = true
}
if trim {
// Remove zeroes
if float64(int(val)) == val {
return AsValue(in.Integer()), nil
}
}
return AsValue(strconv.FormatFloat(val, 'f', decimals, 64)), nil
}
func filterGetdigit(in *Value, param *Value) (*Value, *Error) {
i := param.Integer()
l := len(in.String()) // do NOT use in.Len() here!
if i <= 0 || i > l {
return in, nil
}
return AsValue(in.String()[l-i] - 48), nil
}
const filterIRIChars = "/#%[]=:;$&()+,!?*@'~"
func filterIriencode(in *Value, param *Value) (*Value, *Error) {
var b bytes.Buffer
sin := in.String()
for _, r := range sin {
if strings.IndexRune(filterIRIChars, r) >= 0 {
b.WriteRune(r)
} else {
b.WriteString(url.QueryEscape(string(r)))
}
}
return AsValue(b.String()), nil
}
func filterJoin(in *Value, param *Value) (*Value, *Error) {
if !in.CanSlice() {
return in, nil
}
sep := param.String()
sl := make([]string, 0, in.Len())
for i := 0; i < in.Len(); i++ {
sl = append(sl, in.Index(i).String())
}
return AsValue(strings.Join(sl, sep)), nil
}
func filterLast(in *Value, param *Value) (*Value, *Error) {
if in.CanSlice() && in.Len() > 0 {
return in.Index(in.Len() - 1), nil
}
return AsValue(""), nil
}
func filterUpper(in *Value, param *Value) (*Value, *Error) {
return AsValue(strings.ToUpper(in.String())), nil
}
func filterLower(in *Value, param *Value) (*Value, *Error) {
return AsValue(strings.ToLower(in.String())), nil
}
func filterMakelist(in *Value, param *Value) (*Value, *Error) {
s := in.String()
result := make([]string, 0, len(s))
for _, c := range s {
result = append(result, string(c))
}
return AsValue(result), nil
}
func filterCapfirst(in *Value, param *Value) (*Value, *Error) {
if in.Len() <= 0 {
return AsValue(""), nil
}
t := in.String()
r, size := utf8.DecodeRuneInString(t)
return AsValue(strings.ToUpper(string(r)) + t[size:]), nil
}
func filterCenter(in *Value, param *Value) (*Value, *Error) {
width := param.Integer()
slen := in.Len()
if width <= slen {
return in, nil
}
spaces := width - slen
left := spaces/2 + spaces%2
right := spaces / 2
return AsValue(fmt.Sprintf("%s%s%s", strings.Repeat(" ", left),
in.String(), strings.Repeat(" ", right))), nil
}
func filterDate(in *Value, param *Value) (*Value, *Error) {
t, isTime := in.Interface().(time.Time)
if !isTime {
return nil, &Error{
Sender: "filter:date",
OrigError: errors.New("filter input argument must be of type 'time.Time'"),
}
}
return AsValue(t.Format(param.String())), nil
}
func filterFloat(in *Value, param *Value) (*Value, *Error) {
return AsValue(in.Float()), nil
}
func filterInteger(in *Value, param *Value) (*Value, *Error) {
return AsValue(in.Integer()), nil
}
func filterLinebreaks(in *Value, param *Value) (*Value, *Error) {
if in.Len() == 0 {
return in, nil
}
var b bytes.Buffer
// Newline = <br />
// Double newline = <p>...</p>
lines := strings.Split(in.String(), "\n")
lenlines := len(lines)
opened := false
for idx, line := range lines {
if !opened {
b.WriteString("<p>")
opened = true
}
b.WriteString(line)
if idx < lenlines-1 && strings.TrimSpace(lines[idx]) != "" {
// We've not reached the end
if strings.TrimSpace(lines[idx+1]) == "" {
// Next line is empty
if opened {
b.WriteString("</p>")
opened = false
}
} else {
b.WriteString("<br />")
}
}
}
if opened {
b.WriteString("</p>")
}
return AsValue(b.String()), nil
}
func 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
}
func filterLinenumbers(in *Value, param *Value) (*Value, *Error) {
lines := strings.Split(in.String(), "\n")
output := make([]string, 0, len(lines))
for idx, line := range lines {
output = append(output, fmt.Sprintf("%d. %s", idx+1, line))
}
return AsValue(strings.Join(output, "\n")), nil
}
func filterLjust(in *Value, param *Value) (*Value, *Error) {
times := param.Integer() - in.Len()
if times < 0 {
times = 0
}
return AsValue(fmt.Sprintf("%s%s", in.String(), strings.Repeat(" ", times))), nil
}
func filterUrlencode(in *Value, param *Value) (*Value, *Error) {
return AsValue(url.QueryEscape(in.String())), nil
}
// TODO: This regexp could do some work
var filterUrlizeURLRegexp = regexp.MustCompile(`((((http|https)://)|www\.|((^|[ ])[0-9A-Za-z_\-]+(\.com|\.net|\.org|\.info|\.biz|\.de))))(?U:.*)([ ]+|$)`)
var filterUrlizeEmailRegexp = regexp.MustCompile(`(\w+@\w+\.\w{2,4})`)
func filterUrlizeHelper(input string, autoescape bool, trunc int) (string, error) {
var soutErr error
sout := filterUrlizeURLRegexp.ReplaceAllStringFunc(input, func(raw_url string) string {
var prefix string
var suffix string
if strings.HasPrefix(raw_url, " ") {
prefix = " "
}
if strings.HasSuffix(raw_url, " ") {
suffix = " "
}
raw_url = strings.TrimSpace(raw_url)
t, err := ApplyFilter("iriencode", AsValue(raw_url), nil)
if err != nil {
soutErr = err
return ""
}
url := t.String()
if !strings.HasPrefix(url, "http") {
url = fmt.Sprintf("http://%s", url)
}
title := raw_url
if trunc > 3 && len(title) > trunc {
title = fmt.Sprintf("%s...", title[:trunc-3])
}
if autoescape {
t, err := ApplyFilter("escape", AsValue(title), nil)
if err != nil {
soutErr = err
return ""
}
title = t.String()
}
return fmt.Sprintf(`%s<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 {
title = fmt.Sprintf("%s...", title[:trunc-3])
}
return fmt.Sprintf(`<a href="mailto:%s">%s</a>`, mail, title)
})
return sout, nil
}
func filterUrlize(in *Value, param *Value) (*Value, *Error) {
autoescape := true
if param.IsBool() {
autoescape = param.Bool()
}
s, err := filterUrlizeHelper(in.String(), autoescape, -1)
if err != nil {
}
return AsValue(s), nil
}
func filterUrlizetrunc(in *Value, param *Value) (*Value, *Error) {
s, err := filterUrlizeHelper(in.String(), true, param.Integer())
if err != nil {
return nil, &Error{
Sender: "filter:urlizetrunc",
OrigError: errors.New("you cannot pass more than 2 arguments to filter 'pluralize'"),
}
}
return AsValue(s), nil
}
func filterStringformat(in *Value, param *Value) (*Value, *Error) {
return AsValue(fmt.Sprintf(param.String(), in.Interface())), nil
}
var reStriptags = regexp.MustCompile("<[^>]*?>")
func filterStriptags(in *Value, param *Value) (*Value, *Error) {
s := in.String()
// Strip all tags
s = reStriptags.ReplaceAllString(s, "")
return AsValue(strings.TrimSpace(s)), nil
}
// https://en.wikipedia.org/wiki/Phoneword
var filterPhone2numericMap = map[string]string{
"a": "2", "b": "2", "c": "2", "d": "3", "e": "3", "f": "3", "g": "4", "h": "4", "i": "4", "j": "5", "k": "5",
"l": "5", "m": "6", "n": "6", "o": "6", "p": "7", "q": "7", "r": "7", "s": "7", "t": "8", "u": "8", "v": "8",
"w": "9", "x": "9", "y": "9", "z": "9",
}
func filterPhone2numeric(in *Value, param *Value) (*Value, *Error) {
sin := in.String()
for k, v := range filterPhone2numericMap {
sin = strings.Replace(sin, k, v, -1)
sin = strings.Replace(sin, strings.ToUpper(k), v, -1)
}
return AsValue(sin), nil
}
func filterPluralize(in *Value, param *Value) (*Value, *Error) {
if in.IsNumber() {
// Works only on numbers
if param.Len() > 0 {
endings := strings.Split(param.String(), ",")
if len(endings) > 2 {
return nil, &Error{
Sender: "filter:pluralize",
OrigError: errors.New("you cannot pass more than 2 arguments to filter 'pluralize'"),
}
}
if len(endings) == 1 {
// 1 argument
if in.Integer() != 1 {
return AsValue(endings[0]), nil
}
} else {
if in.Integer() != 1 {
// 2 arguments
return AsValue(endings[1]), nil
}
return AsValue(endings[0]), nil
}
} else {
if in.Integer() != 1 {
// return default 's'
return AsValue("s"), nil
}
}
return AsValue(""), nil
}
return nil, &Error{
Sender: "filter:pluralize",
OrigError: errors.New("filter 'pluralize' does only work on numbers"),
}
}
func filterRandom(in *Value, param *Value) (*Value, *Error) {
if !in.CanSlice() || in.Len() <= 0 {
return in, nil
}
i := rand.Intn(in.Len())
return in.Index(i), nil
}
func filterRemovetags(in *Value, param *Value) (*Value, *Error) {
s := in.String()
tags := strings.Split(param.String(), ",")
// Strip only specific tags
for _, tag := range tags {
re := regexp.MustCompile(fmt.Sprintf("</?%s/?>", tag))
s = re.ReplaceAllString(s, "")
}
return AsValue(strings.TrimSpace(s)), nil
}
func filterRjust(in *Value, param *Value) (*Value, *Error) {
return AsValue(fmt.Sprintf(fmt.Sprintf("%%%ds", param.Integer()), in.String())), nil
}
func filterSlice(in *Value, param *Value) (*Value, *Error) {
comp := strings.Split(param.String(), ":")
if len(comp) != 2 {
return nil, &Error{
Sender: "filter:slice",
OrigError: errors.New("Slice string must have the format 'from:to' [from/to can be omitted, but the ':' is required]"),
}
}
if !in.CanSlice() {
return in, nil
}
from := AsValue(comp[0]).Integer()
to := in.Len()
if from > to {
from = to
}
vto := AsValue(comp[1]).Integer()
if vto >= from && vto <= in.Len() {
to = vto
}
return in.Slice(from, to), nil
}
func filterTitle(in *Value, param *Value) (*Value, *Error) {
if !in.IsString() {
return AsValue(""), nil
}
return AsValue(strings.Title(strings.ToLower(in.String()))), nil
}
func filterWordcount(in *Value, param *Value) (*Value, *Error) {
return AsValue(len(strings.Fields(in.String()))), nil
}
func filterWordwrap(in *Value, param *Value) (*Value, *Error) {
words := strings.Fields(in.String())
wordsLen := len(words)
wrapAt := param.Integer()
if wrapAt <= 0 {
return in, nil
}
linecount := wordsLen/wrapAt + wordsLen%wrapAt
lines := make([]string, 0, linecount)
for i := 0; i < linecount; i++ {
lines = append(lines, strings.Join(words[wrapAt*i:min(wrapAt*(i+1), wordsLen)], " "))
}
return AsValue(strings.Join(lines, "\n")), nil
}
func filterYesno(in *Value, param *Value) (*Value, *Error) {
choices := map[int]string{
0: "yes",
1: "no",
2: "maybe",
}
paramString := param.String()
customChoices := strings.Split(paramString, ",")
if len(paramString) > 0 {
if len(customChoices) > 3 {
return nil, &Error{
Sender: "filter:yesno",
OrigError: errors.Errorf("You cannot pass more than 3 options to the 'yesno'-filter (got: '%s').", paramString),
}
}
if len(customChoices) < 2 {
return nil, &Error{
Sender: "filter:yesno",
OrigError: errors.Errorf("You must pass either no or at least 2 arguments to the 'yesno'-filter (got: '%s').", paramString),
}
}
// Map to the options now
choices[0] = customChoices[0]
choices[1] = customChoices[1]
if len(customChoices) == 3 {
choices[2] = customChoices[2]
}
}
// maybe
if in.IsNil() {
return AsValue(choices[2]), nil
}
// yes
if in.IsTrue() {
return AsValue(choices[0]), nil
}
// no
return AsValue(choices[1]), nil
}

13
vendor/github.com/flosch/pongo2/go.mod generated vendored Normal file
View File

@ -0,0 +1,13 @@
module github.com/flosch/pongo2
require (
github.com/go-check/check v0.0.0-20180628173108-788fd7840127
github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5
github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/mattn/goveralls v0.0.2 // indirect
golang.org/x/tools v0.0.0-20181221001348-537d06c36207 // indirect
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)

15
vendor/github.com/flosch/pongo2/helpers.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
package pongo2
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}

432
vendor/github.com/flosch/pongo2/lexer.go generated vendored Normal file
View File

@ -0,0 +1,432 @@
package pongo2
import (
"fmt"
"strings"
"unicode/utf8"
"github.com/juju/errors"
)
const (
TokenError = iota
EOF
TokenHTML
TokenKeyword
TokenIdentifier
TokenString
TokenNumber
TokenSymbol
)
var (
tokenSpaceChars = " \n\r\t"
tokenIdentifierChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
tokenIdentifierCharsWithDigits = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"
tokenDigits = "0123456789"
// Available symbols in pongo2 (within filters/tag)
TokenSymbols = []string{
// 3-Char symbols
"{{-", "-}}", "{%-", "-%}",
// 2-Char symbols
"==", ">=", "<=", "&&", "||", "{{", "}}", "{%", "%}", "!=", "<>",
// 1-Char symbol
"(", ")", "+", "-", "*", "<", ">", "/", "^", ",", ".", "!", "|", ":", "=", "%",
}
// Available keywords in pongo2
TokenKeywords = []string{"in", "and", "or", "not", "true", "false", "as", "export"}
)
type TokenType int
type Token struct {
Filename string
Typ TokenType
Val string
Line int
Col int
TrimWhitespaces bool
}
type lexerStateFn func() lexerStateFn
type lexer struct {
name string
input string
start int // start pos of the item
pos int // current pos
width int // width of last rune
tokens []*Token
errored bool
startline int
startcol int
line int
col int
inVerbatim bool
verbatimName string
}
func (t *Token) String() string {
val := t.Val
if len(val) > 1000 {
val = fmt.Sprintf("%s...%s", val[:10], val[len(val)-5:len(val)])
}
typ := ""
switch t.Typ {
case TokenHTML:
typ = "HTML"
case TokenError:
typ = "Error"
case TokenIdentifier:
typ = "Identifier"
case TokenKeyword:
typ = "Keyword"
case TokenNumber:
typ = "Number"
case TokenString:
typ = "String"
case TokenSymbol:
typ = "Symbol"
default:
typ = "Unknown"
}
return fmt.Sprintf("<Token Typ=%s (%d) Val='%s' Line=%d Col=%d, WT=%t>",
typ, t.Typ, val, t.Line, t.Col, t.TrimWhitespaces)
}
func lex(name string, input string) ([]*Token, *Error) {
l := &lexer{
name: name,
input: input,
tokens: make([]*Token, 0, 100),
line: 1,
col: 1,
startline: 1,
startcol: 1,
}
l.run()
if l.errored {
errtoken := l.tokens[len(l.tokens)-1]
return nil, &Error{
Filename: name,
Line: errtoken.Line,
Column: errtoken.Col,
Sender: "lexer",
OrigError: errors.New(errtoken.Val),
}
}
return l.tokens, nil
}
func (l *lexer) value() string {
return l.input[l.start:l.pos]
}
func (l *lexer) length() int {
return l.pos - l.start
}
func (l *lexer) emit(t TokenType) {
tok := &Token{
Filename: l.name,
Typ: t,
Val: l.value(),
Line: l.startline,
Col: l.startcol,
}
if t == TokenString {
// Escape sequence \" in strings
tok.Val = strings.Replace(tok.Val, `\"`, `"`, -1)
tok.Val = strings.Replace(tok.Val, `\\`, `\`, -1)
}
if t == TokenSymbol && len(tok.Val) == 3 && (strings.HasSuffix(tok.Val, "-") || strings.HasPrefix(tok.Val, "-")) {
tok.TrimWhitespaces = true
tok.Val = strings.Replace(tok.Val, "-", "", -1)
}
l.tokens = append(l.tokens, tok)
l.start = l.pos
l.startline = l.line
l.startcol = l.col
}
func (l *lexer) next() rune {
if l.pos >= len(l.input) {
l.width = 0
return EOF
}
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
l.width = w
l.pos += l.width
l.col += l.width
return r
}
func (l *lexer) backup() {
l.pos -= l.width
l.col -= l.width
}
func (l *lexer) peek() rune {
r := l.next()
l.backup()
return r
}
func (l *lexer) ignore() {
l.start = l.pos
l.startline = l.line
l.startcol = l.col
}
func (l *lexer) accept(what string) bool {
if strings.IndexRune(what, l.next()) >= 0 {
return true
}
l.backup()
return false
}
func (l *lexer) acceptRun(what string) {
for strings.IndexRune(what, l.next()) >= 0 {
}
l.backup()
}
func (l *lexer) errorf(format string, args ...interface{}) lexerStateFn {
t := &Token{
Filename: l.name,
Typ: TokenError,
Val: fmt.Sprintf(format, args...),
Line: l.startline,
Col: l.startcol,
}
l.tokens = append(l.tokens, t)
l.errored = true
l.startline = l.line
l.startcol = l.col
return nil
}
func (l *lexer) eof() bool {
return l.start >= len(l.input)-1
}
func (l *lexer) run() {
for {
// TODO: Support verbatim tag names
// https://docs.djangoproject.com/en/dev/ref/templates/builtins/#verbatim
if l.inVerbatim {
name := l.verbatimName
if name != "" {
name += " "
}
if strings.HasPrefix(l.input[l.pos:], fmt.Sprintf("{%% endverbatim %s%%}", name)) { // end verbatim
if l.pos > l.start {
l.emit(TokenHTML)
}
w := len("{% endverbatim %}")
l.pos += w
l.col += w
l.ignore()
l.inVerbatim = false
}
} else if strings.HasPrefix(l.input[l.pos:], "{% verbatim %}") { // tag
if l.pos > l.start {
l.emit(TokenHTML)
}
l.inVerbatim = true
w := len("{% verbatim %}")
l.pos += w
l.col += w
l.ignore()
}
if !l.inVerbatim {
// Ignore single-line comments {# ... #}
if strings.HasPrefix(l.input[l.pos:], "{#") {
if l.pos > l.start {
l.emit(TokenHTML)
}
l.pos += 2 // pass '{#'
l.col += 2
for {
switch l.peek() {
case EOF:
l.errorf("Single-line comment not closed.")
return
case '\n':
l.errorf("Newline not permitted in a single-line comment.")
return
}
if strings.HasPrefix(l.input[l.pos:], "#}") {
l.pos += 2 // pass '#}'
l.col += 2
break
}
l.next()
}
l.ignore() // ignore whole comment
// Comment skipped
continue // next token
}
if strings.HasPrefix(l.input[l.pos:], "{{") || // variable
strings.HasPrefix(l.input[l.pos:], "{%") { // tag
if l.pos > l.start {
l.emit(TokenHTML)
}
l.tokenize()
if l.errored {
return
}
continue
}
}
switch l.peek() {
case '\n':
l.line++
l.col = 0
}
if l.next() == EOF {
break
}
}
if l.pos > l.start {
l.emit(TokenHTML)
}
if l.inVerbatim {
l.errorf("verbatim-tag not closed, got EOF.")
}
}
func (l *lexer) tokenize() {
for state := l.stateCode; state != nil; {
state = state()
}
}
func (l *lexer) stateCode() lexerStateFn {
outer_loop:
for {
switch {
case l.accept(tokenSpaceChars):
if l.value() == "\n" {
return l.errorf("Newline not allowed within tag/variable.")
}
l.ignore()
continue
case l.accept(tokenIdentifierChars):
return l.stateIdentifier
case l.accept(tokenDigits):
return l.stateNumber
case l.accept(`"'`):
return l.stateString
}
// Check for symbol
for _, sym := range TokenSymbols {
if strings.HasPrefix(l.input[l.start:], sym) {
l.pos += len(sym)
l.col += l.length()
l.emit(TokenSymbol)
if sym == "%}" || sym == "-%}" || sym == "}}" || sym == "-}}" {
// Tag/variable end, return after emit
return nil
}
continue outer_loop
}
}
break
}
// Normal shut down
return nil
}
func (l *lexer) stateIdentifier() lexerStateFn {
l.acceptRun(tokenIdentifierChars)
l.acceptRun(tokenIdentifierCharsWithDigits)
for _, kw := range TokenKeywords {
if kw == l.value() {
l.emit(TokenKeyword)
return l.stateCode
}
}
l.emit(TokenIdentifier)
return l.stateCode
}
func (l *lexer) stateNumber() lexerStateFn {
l.acceptRun(tokenDigits)
if l.accept(tokenIdentifierCharsWithDigits) {
// This seems to be an identifier starting with a number.
// See https://github.com/flosch/pongo2/issues/151
return l.stateIdentifier()
}
/*
Maybe context-sensitive number lexing?
* comments.0.Text // first comment
* usercomments.1.0 // second user, first comment
* if (score >= 8.5) // 8.5 as a number
if l.peek() == '.' {
l.accept(".")
if !l.accept(tokenDigits) {
return l.errorf("Malformed number.")
}
l.acceptRun(tokenDigits)
}
*/
l.emit(TokenNumber)
return l.stateCode
}
func (l *lexer) stateString() lexerStateFn {
quotationMark := l.value()
l.ignore()
l.startcol-- // we're starting the position at the first "
for !l.accept(quotationMark) {
switch l.next() {
case '\\':
// escape sequence
switch l.peek() {
case '"', '\\':
l.next()
default:
return l.errorf("Unknown escape sequence: \\%c", l.peek())
}
case EOF:
return l.errorf("Unexpected EOF, string not closed.")
case '\n':
return l.errorf("Newline in string is not allowed.")
}
}
l.backup()
l.emit(TokenString)
l.next()
l.ignore()
return l.stateCode
}

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

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

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

@ -0,0 +1,23 @@
package pongo2
import (
"strings"
)
type nodeHTML struct {
token *Token
trimLeft bool
trimRight bool
}
func (n *nodeHTML) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
res := n.token.Val
if n.trimLeft {
res = strings.TrimLeft(res, tokenSpaceChars)
}
if n.trimRight {
res = strings.TrimRight(res, tokenSpaceChars)
}
writer.WriteString(res)
return nil
}

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

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

26
vendor/github.com/flosch/pongo2/options.go generated vendored Normal file
View File

@ -0,0 +1,26 @@
package pongo2
// Options allow you to change the behavior of template-engine.
// You can change the options before calling the Execute method.
type Options struct {
// If this is set to true the first newline after a block is removed (block, not variable tag!). Defaults to false.
TrimBlocks bool
// If this is set to true leading spaces and tabs are stripped from the start of a line to a block. Defaults to false
LStripBlocks bool
}
func newOptions() *Options {
return &Options{
TrimBlocks: false,
LStripBlocks: false,
}
}
// Update updates this options from another options.
func (opt *Options) Update(other *Options) *Options {
opt.TrimBlocks = other.TrimBlocks
opt.LStripBlocks = other.LStripBlocks
return opt
}

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

@ -0,0 +1,309 @@
package pongo2
import (
"fmt"
"strings"
"github.com/juju/errors"
)
type INode interface {
Execute(*ExecutionContext, TemplateWriter) *Error
}
type IEvaluator interface {
INode
GetPositionToken() *Token
Evaluate(*ExecutionContext) (*Value, *Error)
FilterApplied(name string) bool
}
// The parser provides you a comprehensive and easy tool to
// work with the template document and arguments provided by
// the user for your custom tag.
//
// The parser works on a token list which will be provided by pongo2.
// A token is a unit you can work with. Tokens are either of type identifier,
// string, number, keyword, HTML or symbol.
//
// (See Token's documentation for more about tokens)
type Parser struct {
name string
idx int
tokens []*Token
lastToken *Token
// if the parser parses a template document, here will be
// a reference to it (needed to access the template through Tags)
template *Template
}
// Creates a new parser to parse tokens.
// Used inside pongo2 to parse documents and to provide an easy-to-use
// parser for tag authors
func newParser(name string, tokens []*Token, template *Template) *Parser {
p := &Parser{
name: name,
tokens: tokens,
template: template,
}
if len(tokens) > 0 {
p.lastToken = tokens[len(tokens)-1]
}
return p
}
// Consume one token. It will be gone forever.
func (p *Parser) Consume() {
p.ConsumeN(1)
}
// Consume N tokens. They will be gone forever.
func (p *Parser) ConsumeN(count int) {
p.idx += count
}
// Returns the current token.
func (p *Parser) Current() *Token {
return p.Get(p.idx)
}
// Returns the CURRENT token if the given type matches.
// Consumes this token on success.
func (p *Parser) MatchType(typ TokenType) *Token {
if t := p.PeekType(typ); t != nil {
p.Consume()
return t
}
return nil
}
// Returns the CURRENT token if the given type AND value matches.
// Consumes this token on success.
func (p *Parser) Match(typ TokenType, val string) *Token {
if t := p.Peek(typ, val); t != nil {
p.Consume()
return t
}
return nil
}
// Returns the CURRENT token if the given type AND *one* of
// the given values matches.
// Consumes this token on success.
func (p *Parser) MatchOne(typ TokenType, vals ...string) *Token {
for _, val := range vals {
if t := p.Peek(typ, val); t != nil {
p.Consume()
return t
}
}
return nil
}
// Returns the CURRENT token if the given type matches.
// It DOES NOT consume the token.
func (p *Parser) PeekType(typ TokenType) *Token {
return p.PeekTypeN(0, typ)
}
// Returns the CURRENT token if the given type AND value matches.
// It DOES NOT consume the token.
func (p *Parser) Peek(typ TokenType, val string) *Token {
return p.PeekN(0, typ, val)
}
// Returns the CURRENT token if the given type AND *one* of
// the given values matches.
// It DOES NOT consume the token.
func (p *Parser) PeekOne(typ TokenType, vals ...string) *Token {
for _, v := range vals {
t := p.PeekN(0, typ, v)
if t != nil {
return t
}
}
return nil
}
// Returns the tokens[current position + shift] token if the
// given type AND value matches for that token.
// DOES NOT consume the token.
func (p *Parser) PeekN(shift int, typ TokenType, val string) *Token {
t := p.Get(p.idx + shift)
if t != nil {
if t.Typ == typ && t.Val == val {
return t
}
}
return nil
}
// Returns the tokens[current position + shift] token if the given type matches.
// DOES NOT consume the token for that token.
func (p *Parser) PeekTypeN(shift int, typ TokenType) *Token {
t := p.Get(p.idx + shift)
if t != nil {
if t.Typ == typ {
return t
}
}
return nil
}
// Returns the UNCONSUMED token count.
func (p *Parser) Remaining() int {
return len(p.tokens) - p.idx
}
// Returns the total token count.
func (p *Parser) Count() int {
return len(p.tokens)
}
// Returns tokens[i] or NIL (if i >= len(tokens))
func (p *Parser) Get(i int) *Token {
if i < len(p.tokens) && i >= 0 {
return p.tokens[i]
}
return nil
}
// Returns tokens[current-position + shift] or NIL
// (if (current-position + i) >= len(tokens))
func (p *Parser) GetR(shift int) *Token {
i := p.idx + shift
return p.Get(i)
}
// Error produces a nice error message and returns an error-object.
// The 'token'-argument is optional. If provided, it will take
// the token's position information. If not provided, it will
// automatically use the CURRENT token's position information.
func (p *Parser) Error(msg string, token *Token) *Error {
if token == nil {
// Set current token
token = p.Current()
if token == nil {
// Set to last token
if len(p.tokens) > 0 {
token = p.tokens[len(p.tokens)-1]
}
}
}
var line, col int
if token != nil {
line = token.Line
col = token.Col
}
return &Error{
Template: p.template,
Filename: p.name,
Sender: "parser",
Line: line,
Column: col,
Token: token,
OrigError: errors.New(msg),
}
}
// Wraps all nodes between starting tag and "{% endtag %}" and provides
// one simple interface to execute the wrapped nodes.
// It returns a parser to process provided arguments to the tag.
func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) {
wrapper := &NodeWrapper{}
var tagArgs []*Token
for p.Remaining() > 0 {
// New tag, check whether we have to stop wrapping here
if p.Peek(TokenSymbol, "{%") != nil {
tagIdent := p.PeekTypeN(1, TokenIdentifier)
if tagIdent != nil {
// We've found a (!) end-tag
found := false
for _, n := range names {
if tagIdent.Val == n {
found = true
break
}
}
// We only process the tag if we've found an end tag
if found {
// Okay, endtag found.
p.ConsumeN(2) // '{%' tagname
for {
if p.Match(TokenSymbol, "%}") != nil {
// Okay, end the wrapping here
wrapper.Endtag = tagIdent.Val
return wrapper, newParser(p.template.name, tagArgs, p.template), nil
}
t := p.Current()
p.Consume()
if t == nil {
return nil, nil, p.Error("Unexpected EOF.", p.lastToken)
}
tagArgs = append(tagArgs, t)
}
}
}
}
// Otherwise process next element to be wrapped
node, err := p.parseDocElement()
if err != nil {
return nil, nil, err
}
wrapper.nodes = append(wrapper.nodes, node)
}
return nil, nil, p.Error(fmt.Sprintf("Unexpected EOF, expected tag %s.", strings.Join(names, " or ")),
p.lastToken)
}
// Skips all nodes between starting tag and "{% endtag %}"
func (p *Parser) SkipUntilTag(names ...string) *Error {
for p.Remaining() > 0 {
// New tag, check whether we have to stop wrapping here
if p.Peek(TokenSymbol, "{%") != nil {
tagIdent := p.PeekTypeN(1, TokenIdentifier)
if tagIdent != nil {
// We've found a (!) end-tag
found := false
for _, n := range names {
if tagIdent.Val == n {
found = true
break
}
}
// We only process the tag if we've found an end tag
if found {
// Okay, endtag found.
p.ConsumeN(2) // '{%' tagname
for {
if p.Match(TokenSymbol, "%}") != nil {
// Done skipping, exit.
return nil
}
}
}
}
}
t := p.Current()
p.Consume()
if t == nil {
return p.Error("Unexpected EOF.", p.lastToken)
}
}
return p.Error(fmt.Sprintf("Unexpected EOF, expected tag %s.", strings.Join(names, " or ")), p.lastToken)
}

59
vendor/github.com/flosch/pongo2/parser_document.go generated vendored Normal file
View File

@ -0,0 +1,59 @@
package pongo2
// Doc = { ( Filter | Tag | HTML ) }
func (p *Parser) parseDocElement() (INode, *Error) {
t := p.Current()
switch t.Typ {
case TokenHTML:
n := &nodeHTML{token: t}
left := p.PeekTypeN(-1, TokenSymbol)
right := p.PeekTypeN(1, TokenSymbol)
n.trimLeft = left != nil && left.TrimWhitespaces
n.trimRight = right != nil && right.TrimWhitespaces
p.Consume() // consume HTML element
return n, nil
case TokenSymbol:
switch t.Val {
case "{{":
// parse variable
variable, err := p.parseVariableElement()
if err != nil {
return nil, err
}
return variable, nil
case "{%":
// parse tag
tag, err := p.parseTagElement()
if err != nil {
return nil, err
}
return tag, nil
}
}
return nil, p.Error("Unexpected token (only HTML/tags/filters in templates allowed)", t)
}
func (tpl *Template) parse() *Error {
tpl.parser = newParser(tpl.name, tpl.tokens, tpl)
doc, err := tpl.parser.parseDocument()
if err != nil {
return err
}
tpl.root = doc
return nil
}
func (p *Parser) parseDocument() (*nodeDocument, *Error) {
doc := &nodeDocument{}
for p.Remaining() > 0 {
node, err := p.parseDocElement()
if err != nil {
return nil, err
}
doc.Nodes = append(doc.Nodes, node)
}
return doc, nil
}

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

@ -0,0 +1,503 @@
package pongo2
import (
"fmt"
"math"
)
type Expression struct {
// TODO: Add location token?
expr1 IEvaluator
expr2 IEvaluator
opToken *Token
}
type relationalExpression struct {
// TODO: Add location token?
expr1 IEvaluator
expr2 IEvaluator
opToken *Token
}
type simpleExpression struct {
negate bool
negativeSign bool
term1 IEvaluator
term2 IEvaluator
opToken *Token
}
type term struct {
// TODO: Add location token?
factor1 IEvaluator
factor2 IEvaluator
opToken *Token
}
type power struct {
// TODO: Add location token?
power1 IEvaluator
power2 IEvaluator
}
func (expr *Expression) FilterApplied(name string) bool {
return expr.expr1.FilterApplied(name) && (expr.expr2 == nil ||
(expr.expr2 != nil && expr.expr2.FilterApplied(name)))
}
func (expr *relationalExpression) FilterApplied(name string) bool {
return expr.expr1.FilterApplied(name) && (expr.expr2 == nil ||
(expr.expr2 != nil && expr.expr2.FilterApplied(name)))
}
func (expr *simpleExpression) FilterApplied(name string) bool {
return expr.term1.FilterApplied(name) && (expr.term2 == nil ||
(expr.term2 != nil && expr.term2.FilterApplied(name)))
}
func (expr *term) FilterApplied(name string) bool {
return expr.factor1.FilterApplied(name) && (expr.factor2 == nil ||
(expr.factor2 != nil && expr.factor2.FilterApplied(name)))
}
func (expr *power) FilterApplied(name string) bool {
return expr.power1.FilterApplied(name) && (expr.power2 == nil ||
(expr.power2 != nil && expr.power2.FilterApplied(name)))
}
func (expr *Expression) GetPositionToken() *Token {
return expr.expr1.GetPositionToken()
}
func (expr *relationalExpression) GetPositionToken() *Token {
return expr.expr1.GetPositionToken()
}
func (expr *simpleExpression) GetPositionToken() *Token {
return expr.term1.GetPositionToken()
}
func (expr *term) GetPositionToken() *Token {
return expr.factor1.GetPositionToken()
}
func (expr *power) GetPositionToken() *Token {
return expr.power1.GetPositionToken()
}
func (expr *Expression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := expr.Evaluate(ctx)
if err != nil {
return err
}
writer.WriteString(value.String())
return nil
}
func (expr *relationalExpression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := expr.Evaluate(ctx)
if err != nil {
return err
}
writer.WriteString(value.String())
return nil
}
func (expr *simpleExpression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := expr.Evaluate(ctx)
if err != nil {
return err
}
writer.WriteString(value.String())
return nil
}
func (expr *term) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := expr.Evaluate(ctx)
if err != nil {
return err
}
writer.WriteString(value.String())
return nil
}
func (expr *power) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := expr.Evaluate(ctx)
if err != nil {
return err
}
writer.WriteString(value.String())
return nil
}
func (expr *Expression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
v1, err := expr.expr1.Evaluate(ctx)
if err != nil {
return nil, err
}
if expr.expr2 != nil {
switch expr.opToken.Val {
case "and", "&&":
if !v1.IsTrue() {
return AsValue(false), nil
} else {
v2, err := expr.expr2.Evaluate(ctx)
if err != nil {
return nil, err
}
return AsValue(v2.IsTrue()), nil
}
case "or", "||":
if v1.IsTrue() {
return AsValue(true), nil
} else {
v2, err := expr.expr2.Evaluate(ctx)
if err != nil {
return nil, err
}
return AsValue(v2.IsTrue()), nil
}
default:
return nil, ctx.Error(fmt.Sprintf("unimplemented: %s", expr.opToken.Val), expr.opToken)
}
} else {
return v1, nil
}
}
func (expr *relationalExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
v1, err := expr.expr1.Evaluate(ctx)
if err != nil {
return nil, err
}
if expr.expr2 != nil {
v2, err := expr.expr2.Evaluate(ctx)
if err != nil {
return nil, err
}
switch expr.opToken.Val {
case "<=":
if v1.IsFloat() || v2.IsFloat() {
return AsValue(v1.Float() <= v2.Float()), nil
}
return AsValue(v1.Integer() <= v2.Integer()), nil
case ">=":
if v1.IsFloat() || v2.IsFloat() {
return AsValue(v1.Float() >= v2.Float()), 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
}
return AsValue(v1.Integer() > v2.Integer()), nil
case "<":
if v1.IsFloat() || v2.IsFloat() {
return AsValue(v1.Float() < v2.Float()), nil
}
return AsValue(v1.Integer() < v2.Integer()), nil
case "!=", "<>":
return AsValue(!v1.EqualValueTo(v2)), nil
case "in":
return AsValue(v2.Contains(v1)), nil
default:
return nil, ctx.Error(fmt.Sprintf("unimplemented: %s", expr.opToken.Val), expr.opToken)
}
} else {
return v1, nil
}
}
func (expr *simpleExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
t1, err := expr.term1.Evaluate(ctx)
if err != nil {
return nil, err
}
result := t1
if expr.negate {
result = result.Negate()
}
if expr.negativeSign {
if result.IsNumber() {
switch {
case result.IsFloat():
result = AsValue(-1 * result.Float())
case result.IsInteger():
result = AsValue(-1 * result.Integer())
default:
return nil, ctx.Error("Operation between a number and a non-(float/integer) is not possible", nil)
}
} else {
return nil, ctx.Error("Negative sign on a non-number expression", expr.GetPositionToken())
}
}
if expr.term2 != nil {
t2, err := expr.term2.Evaluate(ctx)
if err != nil {
return nil, err
}
switch expr.opToken.Val {
case "+":
if result.IsFloat() || t2.IsFloat() {
// Result will be a float
return AsValue(result.Float() + t2.Float()), nil
}
// Result will be an integer
return AsValue(result.Integer() + t2.Integer()), nil
case "-":
if result.IsFloat() || t2.IsFloat() {
// Result will be a float
return AsValue(result.Float() - t2.Float()), nil
}
// Result will be an integer
return AsValue(result.Integer() - t2.Integer()), nil
default:
return nil, ctx.Error("Unimplemented", expr.GetPositionToken())
}
}
return result, nil
}
func (expr *term) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
f1, err := expr.factor1.Evaluate(ctx)
if err != nil {
return nil, err
}
if expr.factor2 != nil {
f2, err := expr.factor2.Evaluate(ctx)
if err != nil {
return nil, err
}
switch expr.opToken.Val {
case "*":
if f1.IsFloat() || f2.IsFloat() {
// Result will be float
return AsValue(f1.Float() * f2.Float()), nil
}
// Result will be int
return AsValue(f1.Integer() * f2.Integer()), nil
case "/":
if f1.IsFloat() || f2.IsFloat() {
// Result will be float
return AsValue(f1.Float() / f2.Float()), nil
}
// Result will be int
return AsValue(f1.Integer() / f2.Integer()), nil
case "%":
// Result will be int
return AsValue(f1.Integer() % f2.Integer()), nil
default:
return nil, ctx.Error("unimplemented", expr.opToken)
}
} else {
return f1, nil
}
}
func (expr *power) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
p1, err := expr.power1.Evaluate(ctx)
if err != nil {
return nil, err
}
if expr.power2 != nil {
p2, err := expr.power2.Evaluate(ctx)
if err != nil {
return nil, err
}
return AsValue(math.Pow(p1.Float(), p2.Float())), nil
}
return p1, nil
}
func (p *Parser) parseFactor() (IEvaluator, *Error) {
if p.Match(TokenSymbol, "(") != nil {
expr, err := p.ParseExpression()
if err != nil {
return nil, err
}
if p.Match(TokenSymbol, ")") == nil {
return nil, p.Error("Closing bracket expected after expression", nil)
}
return expr, nil
}
return p.parseVariableOrLiteralWithFilter()
}
func (p *Parser) parsePower() (IEvaluator, *Error) {
pw := new(power)
power1, err := p.parseFactor()
if err != nil {
return nil, err
}
pw.power1 = power1
if p.Match(TokenSymbol, "^") != nil {
power2, err := p.parsePower()
if err != nil {
return nil, err
}
pw.power2 = power2
}
if pw.power2 == nil {
// Shortcut for faster evaluation
return pw.power1, nil
}
return pw, nil
}
func (p *Parser) parseTerm() (IEvaluator, *Error) {
returnTerm := new(term)
factor1, err := p.parsePower()
if err != nil {
return nil, err
}
returnTerm.factor1 = factor1
for p.PeekOne(TokenSymbol, "*", "/", "%") != nil {
if returnTerm.opToken != nil {
// Create new sub-term
returnTerm = &term{
factor1: returnTerm,
}
}
op := p.Current()
p.Consume()
factor2, err := p.parsePower()
if err != nil {
return nil, err
}
returnTerm.opToken = op
returnTerm.factor2 = factor2
}
if returnTerm.opToken == nil {
// Shortcut for faster evaluation
return returnTerm.factor1, nil
}
return returnTerm, nil
}
func (p *Parser) parseSimpleExpression() (IEvaluator, *Error) {
expr := new(simpleExpression)
if sign := p.MatchOne(TokenSymbol, "+", "-"); sign != nil {
if sign.Val == "-" {
expr.negativeSign = true
}
}
if p.Match(TokenSymbol, "!") != nil || p.Match(TokenKeyword, "not") != nil {
expr.negate = true
}
term1, err := p.parseTerm()
if err != nil {
return nil, err
}
expr.term1 = term1
for p.PeekOne(TokenSymbol, "+", "-") != nil {
if expr.opToken != nil {
// New sub expr
expr = &simpleExpression{
term1: expr,
}
}
op := p.Current()
p.Consume()
term2, err := p.parseTerm()
if err != nil {
return nil, err
}
expr.term2 = term2
expr.opToken = op
}
if expr.negate == false && expr.negativeSign == false && expr.term2 == nil {
// Shortcut for faster evaluation
return expr.term1, nil
}
return expr, nil
}
func (p *Parser) parseRelationalExpression() (IEvaluator, *Error) {
expr1, err := p.parseSimpleExpression()
if err != nil {
return nil, err
}
expr := &relationalExpression{
expr1: expr1,
}
if t := p.MatchOne(TokenSymbol, "==", "<=", ">=", "!=", "<>", ">", "<"); t != nil {
expr2, err := p.parseRelationalExpression()
if err != nil {
return nil, err
}
expr.opToken = t
expr.expr2 = expr2
} else if t := p.MatchOne(TokenKeyword, "in"); t != nil {
expr2, err := p.parseSimpleExpression()
if err != nil {
return nil, err
}
expr.opToken = t
expr.expr2 = expr2
}
if expr.expr2 == nil {
// Shortcut for faster evaluation
return expr.expr1, nil
}
return expr, nil
}
func (p *Parser) ParseExpression() (IEvaluator, *Error) {
rexpr1, err := p.parseRelationalExpression()
if err != nil {
return nil, err
}
exp := &Expression{
expr1: rexpr1,
}
if p.PeekOne(TokenSymbol, "&&", "||") != nil || p.PeekOne(TokenKeyword, "and", "or") != nil {
op := p.Current()
p.Consume()
expr2, err := p.ParseExpression()
if err != nil {
return nil, err
}
exp.expr2 = expr2
exp.opToken = op
}
if exp.expr2 == nil {
// Shortcut for faster evaluation
return exp.expr1, nil
}
return exp, nil
}

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

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

135
vendor/github.com/flosch/pongo2/tags.go generated vendored Normal file
View File

@ -0,0 +1,135 @@
package pongo2
/* Incomplete:
-----------
verbatim (only the "name" argument is missing for verbatim)
Reconsideration:
----------------
debug (reason: not sure what to output yet)
regroup / Grouping on other properties (reason: maybe too python-specific; not sure how useful this would be in Go)
Following built-in tags wont be added:
--------------------------------------
csrf_token (reason: web-framework specific)
load (reason: python-specific)
url (reason: web-framework specific)
*/
import (
"fmt"
"github.com/juju/errors"
)
type INodeTag interface {
INode
}
// This is the function signature of the tag's parser you will have
// to implement in order to create a new tag.
//
// 'doc' is providing access to the whole document while 'arguments'
// is providing access to the user's arguments to the tag:
//
// {% your_tag_name some "arguments" 123 %}
//
// start_token will be the *Token with the tag's name in it (here: your_tag_name).
//
// Please see the Parser documentation on how to use the parser.
// See RegisterTag()'s documentation for more information about
// writing a tag as well.
type TagParser func(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error)
type tag struct {
name string
parser TagParser
}
var tags map[string]*tag
func init() {
tags = make(map[string]*tag)
}
// Registers a new tag. You usually want to call this
// function in the tag's init() function:
// http://golang.org/doc/effective_go.html#init
//
// See http://www.florian-schlachter.de/post/pongo2/ for more about
// writing filters and tags.
func RegisterTag(name string, parserFn TagParser) error {
_, existing := tags[name]
if existing {
return errors.Errorf("tag with name '%s' is already registered", name)
}
tags[name] = &tag{
name: name,
parser: parserFn,
}
return nil
}
// Replaces an already registered tag with a new implementation. Use this
// function with caution since it allows you to change existing tag behaviour.
func ReplaceTag(name string, parserFn TagParser) error {
_, existing := tags[name]
if !existing {
return errors.Errorf("tag with name '%s' does not exist (therefore cannot be overridden)", name)
}
tags[name] = &tag{
name: name,
parser: parserFn,
}
return nil
}
// Tag = "{%" IDENT ARGS "%}"
func (p *Parser) parseTagElement() (INodeTag, *Error) {
p.Consume() // consume "{%"
tokenName := p.MatchType(TokenIdentifier)
// Check for identifier
if tokenName == nil {
return nil, p.Error("Tag name must be an identifier.", nil)
}
// Check for the existing tag
tag, exists := tags[tokenName.Val]
if !exists {
// Does not exists
return nil, p.Error(fmt.Sprintf("Tag '%s' not found (or beginning tag not provided)", tokenName.Val), tokenName)
}
// Check sandbox tag restriction
if _, isBanned := p.template.set.bannedTags[tokenName.Val]; isBanned {
return nil, p.Error(fmt.Sprintf("Usage of tag '%s' is not allowed (sandbox restriction active).", tokenName.Val), tokenName)
}
var argsToken []*Token
for p.Peek(TokenSymbol, "%}") == nil && p.Remaining() > 0 {
// Add token to args
argsToken = append(argsToken, p.Current())
p.Consume() // next token
}
// EOF?
if p.Remaining() == 0 {
return nil, p.Error("Unexpectedly reached EOF, no tag end found.", p.lastToken)
}
p.Match(TokenSymbol, "%}")
argParser := newParser(p.name, argsToken, p.template)
if len(argsToken) == 0 {
// This is done to have nice EOF error messages
argParser.lastToken = tokenName
}
p.template.level++
defer func() { p.template.level-- }()
return tag.parser(p, tokenName, argParser)
}

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

@ -0,0 +1,52 @@
package pongo2
type tagAutoescapeNode struct {
wrapper *NodeWrapper
autoescape bool
}
func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
old := ctx.Autoescape
ctx.Autoescape = node.autoescape
err := node.wrapper.Execute(ctx, writer)
if err != nil {
return err
}
ctx.Autoescape = old
return nil
}
func tagAutoescapeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
autoescapeNode := &tagAutoescapeNode{}
wrapper, _, err := doc.WrapUntilTag("endautoescape")
if err != nil {
return nil, err
}
autoescapeNode.wrapper = wrapper
modeToken := arguments.MatchType(TokenIdentifier)
if modeToken == nil {
return nil, arguments.Error("A mode is required for autoescape-tag.", nil)
}
if modeToken.Val == "on" {
autoescapeNode.autoescape = true
} else if modeToken.Val == "off" {
autoescapeNode.autoescape = false
} else {
return nil, arguments.Error("Only 'on' or 'off' is valid as an autoescape-mode.", nil)
}
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed autoescape-tag arguments.", nil)
}
return autoescapeNode, nil
}
func init() {
RegisterTag("autoescape", tagAutoescapeParser)
}

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

@ -0,0 +1,129 @@
package pongo2
import (
"bytes"
"fmt"
)
type tagBlockNode struct {
name string
}
func (node *tagBlockNode) getBlockWrappers(tpl *Template) []*NodeWrapper {
nodeWrappers := make([]*NodeWrapper, 0)
var t *NodeWrapper
for tpl != nil {
t = tpl.blocks[node.name]
if t != nil {
nodeWrappers = append(nodeWrappers, t)
}
tpl = tpl.child
}
return nodeWrappers
}
func (node *tagBlockNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
tpl := ctx.template
if tpl == nil {
panic("internal error: tpl == nil")
}
// Determine the block to execute
blockWrappers := node.getBlockWrappers(tpl)
lenBlockWrappers := len(blockWrappers)
if lenBlockWrappers == 0 {
return ctx.Error("internal error: len(block_wrappers) == 0 in tagBlockNode.Execute()", nil)
}
blockWrapper := blockWrappers[lenBlockWrappers-1]
ctx.Private["block"] = tagBlockInformation{
ctx: ctx,
wrappers: blockWrappers[0 : lenBlockWrappers-1],
}
err := blockWrapper.Execute(ctx, writer)
if err != nil {
return err
}
return nil
}
type tagBlockInformation struct {
ctx *ExecutionContext
wrappers []*NodeWrapper
}
func (t tagBlockInformation) Super() string {
lenWrappers := len(t.wrappers)
if lenWrappers == 0 {
return ""
}
superCtx := NewChildExecutionContext(t.ctx)
superCtx.Private["block"] = tagBlockInformation{
ctx: t.ctx,
wrappers: t.wrappers[0 : lenWrappers-1],
}
blockWrapper := t.wrappers[lenWrappers-1]
buf := bytes.NewBufferString("")
err := blockWrapper.Execute(superCtx, &templateWriter{buf})
if err != nil {
return ""
}
return buf.String()
}
func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
if arguments.Count() == 0 {
return nil, arguments.Error("Tag 'block' requires an identifier.", nil)
}
nameToken := arguments.MatchType(TokenIdentifier)
if nameToken == nil {
return nil, arguments.Error("First argument for tag 'block' must be an identifier.", nil)
}
if arguments.Remaining() != 0 {
return nil, arguments.Error("Tag 'block' takes exactly 1 argument (an identifier).", nil)
}
wrapper, endtagargs, err := doc.WrapUntilTag("endblock")
if err != nil {
return nil, err
}
if endtagargs.Remaining() > 0 {
endtagnameToken := endtagargs.MatchType(TokenIdentifier)
if endtagnameToken != nil {
if endtagnameToken.Val != nameToken.Val {
return nil, endtagargs.Error(fmt.Sprintf("Name for 'endblock' must equal to 'block'-tag's name ('%s' != '%s').",
nameToken.Val, endtagnameToken.Val), nil)
}
}
if endtagnameToken == nil || endtagargs.Remaining() > 0 {
return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endblock'.", nil)
}
}
tpl := doc.template
if tpl == nil {
panic("internal error: tpl == nil")
}
_, hasBlock := tpl.blocks[nameToken.Val]
if !hasBlock {
tpl.blocks[nameToken.Val] = wrapper
} else {
return nil, arguments.Error(fmt.Sprintf("Block named '%s' already defined", nameToken.Val), nil)
}
return &tagBlockNode{name: nameToken.Val}, nil
}
func init() {
RegisterTag("block", tagBlockParser)
}

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

@ -0,0 +1,27 @@
package pongo2
type tagCommentNode struct{}
func (node *tagCommentNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
return nil
}
func tagCommentParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
commentNode := &tagCommentNode{}
// TODO: Process the endtag's arguments (see django 'comment'-tag documentation)
err := doc.SkipUntilTag("endcomment")
if err != nil {
return nil, err
}
if arguments.Count() != 0 {
return nil, arguments.Error("Tag 'comment' does not take any argument.", nil)
}
return commentNode, nil
}
func init() {
RegisterTag("comment", tagCommentParser)
}

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

@ -0,0 +1,106 @@
package pongo2
type tagCycleValue struct {
node *tagCycleNode
value *Value
}
type tagCycleNode struct {
position *Token
args []IEvaluator
idx int
asName string
silent bool
}
func (cv *tagCycleValue) String() string {
return cv.value.String()
}
func (node *tagCycleNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
item := node.args[node.idx%len(node.args)]
node.idx++
val, err := item.Evaluate(ctx)
if err != nil {
return err
}
if t, ok := val.Interface().(*tagCycleValue); ok {
// {% cycle "test1" "test2"
// {% cycle cycleitem %}
// Update the cycle value with next value
item := t.node.args[t.node.idx%len(t.node.args)]
t.node.idx++
val, err := item.Evaluate(ctx)
if err != nil {
return err
}
t.value = val
if !t.node.silent {
writer.WriteString(val.String())
}
} else {
// Regular call
cycleValue := &tagCycleValue{
node: node,
value: val,
}
if node.asName != "" {
ctx.Private[node.asName] = cycleValue
}
if !node.silent {
writer.WriteString(val.String())
}
}
return nil
}
// HINT: We're not supporting the old comma-separated list of expressions argument-style
func tagCycleParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
cycleNode := &tagCycleNode{
position: start,
}
for arguments.Remaining() > 0 {
node, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
cycleNode.args = append(cycleNode.args, node)
if arguments.MatchOne(TokenKeyword, "as") != nil {
// as
nameToken := arguments.MatchType(TokenIdentifier)
if nameToken == nil {
return nil, arguments.Error("Name (identifier) expected after 'as'.", nil)
}
cycleNode.asName = nameToken.Val
if arguments.MatchOne(TokenIdentifier, "silent") != nil {
cycleNode.silent = true
}
// Now we're finished
break
}
}
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed cycle-tag.", nil)
}
return cycleNode, nil
}
func init() {
RegisterTag("cycle", tagCycleParser)
}

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

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

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

@ -0,0 +1,95 @@
package pongo2
import (
"bytes"
)
type nodeFilterCall struct {
name string
paramExpr IEvaluator
}
type tagFilterNode struct {
position *Token
bodyWrapper *NodeWrapper
filterChain []*nodeFilterCall
}
func (node *tagFilterNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
temp := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB size
err := node.bodyWrapper.Execute(ctx, temp)
if err != nil {
return err
}
value := AsValue(temp.String())
for _, call := range node.filterChain {
var param *Value
if call.paramExpr != nil {
param, err = call.paramExpr.Evaluate(ctx)
if err != nil {
return err
}
} else {
param = AsValue(nil)
}
value, err = ApplyFilter(call.name, value, param)
if err != nil {
return ctx.Error(err.Error(), node.position)
}
}
writer.WriteString(value.String())
return nil
}
func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
filterNode := &tagFilterNode{
position: start,
}
wrapper, _, err := doc.WrapUntilTag("endfilter")
if err != nil {
return nil, err
}
filterNode.bodyWrapper = wrapper
for arguments.Remaining() > 0 {
filterCall := &nodeFilterCall{}
nameToken := arguments.MatchType(TokenIdentifier)
if nameToken == nil {
return nil, arguments.Error("Expected a filter name (identifier).", nil)
}
filterCall.name = nameToken.Val
if arguments.MatchOne(TokenSymbol, ":") != nil {
// Filter parameter
// NOTICE: we can't use ParseExpression() here, because it would parse the next filter "|..." as well in the argument list
expr, err := arguments.parseVariableOrLiteral()
if err != nil {
return nil, err
}
filterCall.paramExpr = expr
}
filterNode.filterChain = append(filterNode.filterChain, filterCall)
if arguments.MatchOne(TokenSymbol, "|") == nil {
break
}
}
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed filter-tag arguments.", nil)
}
return filterNode, nil
}
func init() {
RegisterTag("filter", tagFilterParser)
}

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

@ -0,0 +1,49 @@
package pongo2
type tagFirstofNode struct {
position *Token
args []IEvaluator
}
func (node *tagFirstofNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
for _, arg := range node.args {
val, err := arg.Evaluate(ctx)
if err != nil {
return err
}
if val.IsTrue() {
if ctx.Autoescape && !arg.FilterApplied("safe") {
val, err = ApplyFilter("escape", val, nil)
if err != nil {
return err
}
}
writer.WriteString(val.String())
return nil
}
}
return nil
}
func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
firstofNode := &tagFirstofNode{
position: start,
}
for arguments.Remaining() > 0 {
node, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
firstofNode.args = append(firstofNode.args, node)
}
return firstofNode, nil
}
func init() {
RegisterTag("firstof", tagFirstofParser)
}

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

@ -0,0 +1,159 @@
package pongo2
type tagForNode struct {
key string
value string // only for maps: for key, value in map
objectEvaluator IEvaluator
reversed bool
sorted bool
bodyWrapper *NodeWrapper
emptyWrapper *NodeWrapper
}
type tagForLoopInformation struct {
Counter int
Counter0 int
Revcounter int
Revcounter0 int
First bool
Last bool
Parentloop *tagForLoopInformation
}
func (node *tagForNode) Execute(ctx *ExecutionContext, writer TemplateWriter) (forError *Error) {
// Backup forloop (as parentloop in public context), key-name and value-name
forCtx := NewChildExecutionContext(ctx)
parentloop := forCtx.Private["forloop"]
// Create loop struct
loopInfo := &tagForLoopInformation{
First: true,
}
// Is it a loop in a loop?
if parentloop != nil {
loopInfo.Parentloop = parentloop.(*tagForLoopInformation)
}
// Register loopInfo in public context
forCtx.Private["forloop"] = loopInfo
obj, err := node.objectEvaluator.Evaluate(forCtx)
if err != nil {
return err
}
obj.IterateOrder(func(idx, count int, key, value *Value) bool {
// There's something to iterate over (correct type and at least 1 item)
// Update loop infos and public context
forCtx.Private[node.key] = key
if value != nil {
forCtx.Private[node.value] = value
}
loopInfo.Counter = idx + 1
loopInfo.Counter0 = idx
if idx == 1 {
loopInfo.First = false
}
if idx+1 == count {
loopInfo.Last = true
}
loopInfo.Revcounter = count - idx // TODO: Not sure about this, have to look it up
loopInfo.Revcounter0 = count - (idx + 1) // TODO: Not sure about this, have to look it up
// Render elements with updated context
err := node.bodyWrapper.Execute(forCtx, writer)
if err != nil {
forError = err
return false
}
return true
}, func() {
// Nothing to iterate over (maybe wrong type or no items)
if node.emptyWrapper != nil {
err := node.emptyWrapper.Execute(forCtx, writer)
if err != nil {
forError = err
}
}
}, node.reversed, node.sorted)
return forError
}
func tagForParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
forNode := &tagForNode{}
// Arguments parsing
var valueToken *Token
keyToken := arguments.MatchType(TokenIdentifier)
if keyToken == nil {
return nil, arguments.Error("Expected an key identifier as first argument for 'for'-tag", nil)
}
if arguments.Match(TokenSymbol, ",") != nil {
// Value name is provided
valueToken = arguments.MatchType(TokenIdentifier)
if valueToken == nil {
return nil, arguments.Error("Value name must be an identifier.", nil)
}
}
if arguments.Match(TokenKeyword, "in") == nil {
return nil, arguments.Error("Expected keyword 'in'.", nil)
}
objectEvaluator, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
forNode.objectEvaluator = objectEvaluator
forNode.key = keyToken.Val
if valueToken != nil {
forNode.value = valueToken.Val
}
if arguments.MatchOne(TokenIdentifier, "reversed") != nil {
forNode.reversed = true
}
if arguments.MatchOne(TokenIdentifier, "sorted") != nil {
forNode.sorted = true
}
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed for-loop arguments.", nil)
}
// Body wrapping
wrapper, endargs, err := doc.WrapUntilTag("empty", "endfor")
if err != nil {
return nil, err
}
forNode.bodyWrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
if wrapper.Endtag == "empty" {
// if there's an else in the if-statement, we need the else-Block as well
wrapper, endargs, err = doc.WrapUntilTag("endfor")
if err != nil {
return nil, err
}
forNode.emptyWrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
}
return forNode, nil
}
func init() {
RegisterTag("for", tagForParser)
}

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

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

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

@ -0,0 +1,116 @@
package pongo2
import (
"bytes"
)
type tagIfchangedNode struct {
watchedExpr []IEvaluator
lastValues []*Value
lastContent []byte
thenWrapper *NodeWrapper
elseWrapper *NodeWrapper
}
func (node *tagIfchangedNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
if len(node.watchedExpr) == 0 {
// Check against own rendered body
buf := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB
err := node.thenWrapper.Execute(ctx, buf)
if err != nil {
return err
}
bufBytes := buf.Bytes()
if !bytes.Equal(node.lastContent, bufBytes) {
// Rendered content changed, output it
writer.Write(bufBytes)
node.lastContent = bufBytes
}
} else {
nowValues := make([]*Value, 0, len(node.watchedExpr))
for _, expr := range node.watchedExpr {
val, err := expr.Evaluate(ctx)
if err != nil {
return err
}
nowValues = append(nowValues, val)
}
// Compare old to new values now
changed := len(node.lastValues) == 0
for idx, oldVal := range node.lastValues {
if !oldVal.EqualValueTo(nowValues[idx]) {
changed = true
break // we can stop here because ONE value changed
}
}
node.lastValues = nowValues
if changed {
// Render thenWrapper
err := node.thenWrapper.Execute(ctx, writer)
if err != nil {
return err
}
} else {
// Render elseWrapper
err := node.elseWrapper.Execute(ctx, writer)
if err != nil {
return err
}
}
}
return nil
}
func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
ifchangedNode := &tagIfchangedNode{}
for arguments.Remaining() > 0 {
// Parse condition
expr, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
ifchangedNode.watchedExpr = append(ifchangedNode.watchedExpr, expr)
}
if arguments.Remaining() > 0 {
return nil, arguments.Error("Ifchanged-arguments are malformed.", nil)
}
// Wrap then/else-blocks
wrapper, endargs, err := doc.WrapUntilTag("else", "endifchanged")
if err != nil {
return nil, err
}
ifchangedNode.thenWrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
if wrapper.Endtag == "else" {
// if there's an else in the if-statement, we need the else-Block as well
wrapper, endargs, err = doc.WrapUntilTag("endifchanged")
if err != nil {
return nil, err
}
ifchangedNode.elseWrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
}
return ifchangedNode, nil
}
func init() {
RegisterTag("ifchanged", tagIfchangedParser)
}

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

@ -0,0 +1,78 @@
package pongo2
type tagIfEqualNode struct {
var1, var2 IEvaluator
thenWrapper *NodeWrapper
elseWrapper *NodeWrapper
}
func (node *tagIfEqualNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
r1, err := node.var1.Evaluate(ctx)
if err != nil {
return err
}
r2, err := node.var2.Evaluate(ctx)
if err != nil {
return err
}
result := r1.EqualValueTo(r2)
if result {
return node.thenWrapper.Execute(ctx, writer)
}
if node.elseWrapper != nil {
return node.elseWrapper.Execute(ctx, writer)
}
return nil
}
func tagIfEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
ifequalNode := &tagIfEqualNode{}
// Parse two expressions
var1, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
var2, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
ifequalNode.var1 = var1
ifequalNode.var2 = var2
if arguments.Remaining() > 0 {
return nil, arguments.Error("ifequal only takes 2 arguments.", nil)
}
// Wrap then/else-blocks
wrapper, endargs, err := doc.WrapUntilTag("else", "endifequal")
if err != nil {
return nil, err
}
ifequalNode.thenWrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
if wrapper.Endtag == "else" {
// if there's an else in the if-statement, we need the else-Block as well
wrapper, endargs, err = doc.WrapUntilTag("endifequal")
if err != nil {
return nil, err
}
ifequalNode.elseWrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
}
return ifequalNode, nil
}
func init() {
RegisterTag("ifequal", tagIfEqualParser)
}

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

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

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

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

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

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

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

@ -0,0 +1,133 @@
package pongo2
import (
"math/rand"
"strings"
"time"
"github.com/juju/errors"
)
var (
tagLoremParagraphs = strings.Split(tagLoremText, "\n")
tagLoremWords = strings.Fields(tagLoremText)
)
type tagLoremNode struct {
position *Token
count int // number of paragraphs
method string // w = words, p = HTML paragraphs, b = plain-text (default is b)
random bool // does not use the default paragraph "Lorem ipsum dolor sit amet, ..."
}
func (node *tagLoremNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
switch node.method {
case "b":
if node.random {
for i := 0; i < node.count; i++ {
if i > 0 {
writer.WriteString("\n")
}
par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))]
writer.WriteString(par)
}
} else {
for i := 0; i < node.count; i++ {
if i > 0 {
writer.WriteString("\n")
}
par := tagLoremParagraphs[i%len(tagLoremParagraphs)]
writer.WriteString(par)
}
}
case "w":
if node.random {
for i := 0; i < node.count; i++ {
if i > 0 {
writer.WriteString(" ")
}
word := tagLoremWords[rand.Intn(len(tagLoremWords))]
writer.WriteString(word)
}
} else {
for i := 0; i < node.count; i++ {
if i > 0 {
writer.WriteString(" ")
}
word := tagLoremWords[i%len(tagLoremWords)]
writer.WriteString(word)
}
}
case "p":
if node.random {
for i := 0; i < node.count; i++ {
if i > 0 {
writer.WriteString("\n")
}
writer.WriteString("<p>")
par := tagLoremParagraphs[rand.Intn(len(tagLoremParagraphs))]
writer.WriteString(par)
writer.WriteString("</p>")
}
} else {
for i := 0; i < node.count; i++ {
if i > 0 {
writer.WriteString("\n")
}
writer.WriteString("<p>")
par := tagLoremParagraphs[i%len(tagLoremParagraphs)]
writer.WriteString(par)
writer.WriteString("</p>")
}
}
default:
return ctx.OrigError(errors.Errorf("unsupported method: %s", node.method), nil)
}
return nil
}
func tagLoremParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
loremNode := &tagLoremNode{
position: start,
count: 1,
method: "b",
}
if countToken := arguments.MatchType(TokenNumber); countToken != nil {
loremNode.count = AsValue(countToken.Val).Integer()
}
if methodToken := arguments.MatchType(TokenIdentifier); methodToken != nil {
if methodToken.Val != "w" && methodToken.Val != "p" && methodToken.Val != "b" {
return nil, arguments.Error("lorem-method must be either 'w', 'p' or 'b'.", nil)
}
loremNode.method = methodToken.Val
}
if arguments.MatchOne(TokenIdentifier, "random") != nil {
loremNode.random = true
}
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed lorem-tag arguments.", nil)
}
return loremNode, nil
}
func init() {
rand.Seed(time.Now().Unix())
RegisterTag("lorem", tagLoremParser)
}
const tagLoremText = `Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.
At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.
Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.`

149
vendor/github.com/flosch/pongo2/tags_macro.go generated vendored Normal file
View File

@ -0,0 +1,149 @@
package pongo2
import (
"bytes"
"fmt"
)
type tagMacroNode struct {
position *Token
name string
argsOrder []string
args map[string]IEvaluator
exported bool
wrapper *NodeWrapper
}
func (node *tagMacroNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
ctx.Private[node.name] = func(args ...*Value) *Value {
return node.call(ctx, args...)
}
return nil
}
func (node *tagMacroNode) call(ctx *ExecutionContext, args ...*Value) *Value {
argsCtx := make(Context)
for k, v := range node.args {
if v == nil {
// User did not provided a default value
argsCtx[k] = nil
} else {
// Evaluate the default value
valueExpr, err := v.Evaluate(ctx)
if err != nil {
ctx.Logf(err.Error())
return AsSafeValue(err.Error())
}
argsCtx[k] = valueExpr
}
}
if len(args) > len(node.argsOrder) {
// Too many arguments, we're ignoring them and just logging into debug mode.
err := ctx.Error(fmt.Sprintf("Macro '%s' called with too many arguments (%d instead of %d).",
node.name, len(args), len(node.argsOrder)), nil).updateFromTokenIfNeeded(ctx.template, node.position)
ctx.Logf(err.Error()) // TODO: This is a workaround, because the error is not returned yet to the Execution()-methods
return AsSafeValue(err.Error())
}
// Make a context for the macro execution
macroCtx := NewChildExecutionContext(ctx)
// Register all arguments in the private context
macroCtx.Private.Update(argsCtx)
for idx, argValue := range args {
macroCtx.Private[node.argsOrder[idx]] = argValue.Interface()
}
var b bytes.Buffer
err := node.wrapper.Execute(macroCtx, &b)
if err != nil {
return AsSafeValue(err.updateFromTokenIfNeeded(ctx.template, node.position).Error())
}
return AsSafeValue(b.String())
}
func tagMacroParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
macroNode := &tagMacroNode{
position: start,
args: make(map[string]IEvaluator),
}
nameToken := arguments.MatchType(TokenIdentifier)
if nameToken == nil {
return nil, arguments.Error("Macro-tag needs at least an identifier as name.", nil)
}
macroNode.name = nameToken.Val
if arguments.MatchOne(TokenSymbol, "(") == nil {
return nil, arguments.Error("Expected '('.", nil)
}
for arguments.Match(TokenSymbol, ")") == nil {
argNameToken := arguments.MatchType(TokenIdentifier)
if argNameToken == nil {
return nil, arguments.Error("Expected argument name as identifier.", nil)
}
macroNode.argsOrder = append(macroNode.argsOrder, argNameToken.Val)
if arguments.Match(TokenSymbol, "=") != nil {
// Default expression follows
argDefaultExpr, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
macroNode.args[argNameToken.Val] = argDefaultExpr
} else {
// No default expression
macroNode.args[argNameToken.Val] = nil
}
if arguments.Match(TokenSymbol, ")") != nil {
break
}
if arguments.Match(TokenSymbol, ",") == nil {
return nil, arguments.Error("Expected ',' or ')'.", nil)
}
}
if arguments.Match(TokenKeyword, "export") != nil {
macroNode.exported = true
}
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed macro-tag.", nil)
}
// Body wrapping
wrapper, endargs, err := doc.WrapUntilTag("endmacro")
if err != nil {
return nil, err
}
macroNode.wrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
if macroNode.exported {
// Now register the macro if it wants to be exported
_, has := doc.template.exportedMacros[macroNode.name]
if has {
return nil, doc.Error(fmt.Sprintf("another macro with name '%s' already exported", macroNode.name), start)
}
doc.template.exportedMacros[macroNode.name] = macroNode
}
return macroNode, nil
}
func init() {
RegisterTag("macro", tagMacroParser)
}

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

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

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

@ -0,0 +1,50 @@
package pongo2
type tagSetNode struct {
name string
expression IEvaluator
}
func (node *tagSetNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
// Evaluate expression
value, err := node.expression.Evaluate(ctx)
if err != nil {
return err
}
ctx.Private[node.name] = value
return nil
}
func tagSetParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
node := &tagSetNode{}
// Parse variable name
typeToken := arguments.MatchType(TokenIdentifier)
if typeToken == nil {
return nil, arguments.Error("Expected an identifier.", nil)
}
node.name = typeToken.Val
if arguments.Match(TokenSymbol, "=") == nil {
return nil, arguments.Error("Expected '='.", nil)
}
// Variable expression
keyExpression, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
node.expression = keyExpression
// Remaining arguments
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed 'set'-tag arguments.", nil)
}
return node, nil
}
func init() {
RegisterTag("set", tagSetParser)
}

54
vendor/github.com/flosch/pongo2/tags_spaceless.go generated vendored Normal file
View File

@ -0,0 +1,54 @@
package pongo2
import (
"bytes"
"regexp"
)
type tagSpacelessNode struct {
wrapper *NodeWrapper
}
var tagSpacelessRegexp = regexp.MustCompile(`(?U:(<.*>))([\t\n\v\f\r ]+)(?U:(<.*>))`)
func (node *tagSpacelessNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
b := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB
err := node.wrapper.Execute(ctx, b)
if err != nil {
return err
}
s := b.String()
// Repeat this recursively
changed := true
for changed {
s2 := tagSpacelessRegexp.ReplaceAllString(s, "$1$3")
changed = s != s2
s = s2
}
writer.WriteString(s)
return nil
}
func tagSpacelessParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
spacelessNode := &tagSpacelessNode{}
wrapper, _, err := doc.WrapUntilTag("endspaceless")
if err != nil {
return nil, err
}
spacelessNode.wrapper = wrapper
if arguments.Remaining() > 0 {
return nil, arguments.Error("Malformed spaceless-tag arguments.", nil)
}
return spacelessNode, nil
}
func init() {
RegisterTag("spaceless", tagSpacelessParser)
}

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

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

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

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

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

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

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

@ -0,0 +1,88 @@
package pongo2
type tagWithNode struct {
withPairs map[string]IEvaluator
wrapper *NodeWrapper
}
func (node *tagWithNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
//new context for block
withctx := NewChildExecutionContext(ctx)
// Put all custom with-pairs into the context
for key, value := range node.withPairs {
val, err := value.Evaluate(ctx)
if err != nil {
return err
}
withctx.Private[key] = val
}
return node.wrapper.Execute(withctx, writer)
}
func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
withNode := &tagWithNode{
withPairs: make(map[string]IEvaluator),
}
if arguments.Count() == 0 {
return nil, arguments.Error("Tag 'with' requires at least one argument.", nil)
}
wrapper, endargs, err := doc.WrapUntilTag("endwith")
if err != nil {
return nil, err
}
withNode.wrapper = wrapper
if endargs.Count() > 0 {
return nil, endargs.Error("Arguments not allowed here.", nil)
}
// Scan through all arguments to see which style the user uses (old or new style).
// If we find any "as" keyword we will enforce old style; otherwise we will use new style.
oldStyle := false // by default we're using the new_style
for i := 0; i < arguments.Count(); i++ {
if arguments.PeekN(i, TokenKeyword, "as") != nil {
oldStyle = true
break
}
}
for arguments.Remaining() > 0 {
if oldStyle {
valueExpr, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
if arguments.Match(TokenKeyword, "as") == nil {
return nil, arguments.Error("Expected 'as' keyword.", nil)
}
keyToken := arguments.MatchType(TokenIdentifier)
if keyToken == nil {
return nil, arguments.Error("Expected an identifier", nil)
}
withNode.withPairs[keyToken.Val] = valueExpr
} else {
keyToken := arguments.MatchType(TokenIdentifier)
if keyToken == nil {
return nil, arguments.Error("Expected an identifier", nil)
}
if arguments.Match(TokenSymbol, "=") == nil {
return nil, arguments.Error("Expected '='.", nil)
}
valueExpr, err := arguments.ParseExpression()
if err != nil {
return nil, err
}
withNode.withPairs[keyToken.Val] = valueExpr
}
}
return withNode, nil
}
func init() {
RegisterTag("with", tagWithParser)
}

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

@ -0,0 +1,277 @@
package pongo2
import (
"bytes"
"io"
"strings"
"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
// Calculation
tokens []*Token
parser *Parser
// first come, first serve (it's important to not override existing entries in here)
level int
parent *Template
child *Template
blocks map[string]*NodeWrapper
exportedMacros map[string]*tagMacroNode
// Output
root *nodeDocument
// Options allow you to change the behavior of template-engine.
// You can change the options before calling the Execute method.
Options *Options
}
func newTemplateString(set *TemplateSet, tpl []byte) (*Template, error) {
return newTemplate(set, "<string>", true, tpl)
}
func newTemplate(set *TemplateSet, name string, isTplString bool, tpl []byte) (*Template, error) {
strTpl := string(tpl)
// Create the template
t := &Template{
set: set,
isTplString: isTplString,
name: name,
tpl: strTpl,
size: len(strTpl),
blocks: make(map[string]*NodeWrapper),
exportedMacros: make(map[string]*tagMacroNode),
Options: newOptions(),
}
// Copy all settings from another Options.
t.Options.Update(set.Options)
// Tokenize it
tokens, err := lex(name, strTpl)
if err != nil {
return nil, err
}
t.tokens = tokens
// For debugging purposes, show all tokens:
/*for i, t := range tokens {
fmt.Printf("%3d. %s\n", i, t)
}*/
// Parse it
err = t.parse()
if err != nil {
return nil, err
}
return t, nil
}
func (tpl *Template) newContextForExecution(context Context) (*Template, *ExecutionContext, error) {
if tpl.Options.TrimBlocks || tpl.Options.LStripBlocks {
// Issue #94 https://github.com/flosch/pongo2/issues/94
// If an application configures pongo2 template to trim_blocks,
// the first newline after a template tag is removed automatically (like in PHP).
prev := &Token{
Typ: TokenHTML,
Val: "\n",
}
for _, t := range tpl.tokens {
if tpl.Options.LStripBlocks {
if prev.Typ == TokenHTML && t.Typ != TokenHTML && t.Val == "{%" {
prev.Val = strings.TrimRight(prev.Val, "\t ")
}
}
if tpl.Options.TrimBlocks {
if prev.Typ != TokenHTML && t.Typ == TokenHTML && prev.Val == "%}" {
if len(t.Val) > 0 && t.Val[0] == '\n' {
t.Val = t.Val[1:len(t.Val)]
}
}
}
prev = t
}
}
// Determine the parent to be executed (for template inheritance)
parent := tpl
for parent.parent != nil {
parent = parent.parent
}
// Create context if none is given
newContext := make(Context)
newContext.Update(tpl.set.Globals)
if context != nil {
newContext.Update(context)
if len(newContext) > 0 {
// Check for context name syntax
err := newContext.checkForValidIdentifiers()
if err != nil {
return parent, nil, err
}
// Check for clashes with macro names
for k := range newContext {
_, has := tpl.exportedMacros[k]
if has {
return parent, nil, &Error{
Filename: tpl.name,
Sender: "execution",
OrigError: errors.Errorf("context key name '%s' clashes with macro '%s'", k, k),
}
}
}
}
}
// Create operational context
ctx := newExecutionContext(parent, newContext)
return parent, ctx, nil
}
func (tpl *Template) execute(context Context, writer TemplateWriter) error {
parent, ctx, err := tpl.newContextForExecution(context)
if err != nil {
return err
}
// Run the selected document
if err := parent.root.Execute(ctx, writer); err != nil {
return err
}
return nil
}
func (tpl *Template) newTemplateWriterAndExecute(context Context, writer io.Writer) error {
return tpl.execute(context, &templateWriter{w: writer})
}
func (tpl *Template) newBufferAndExecute(context Context) (*bytes.Buffer, error) {
// Create output buffer
// We assume that the rendered template will be 30% larger
buffer := bytes.NewBuffer(make([]byte, 0, int(float64(tpl.size)*1.3)))
if err := tpl.execute(context, buffer); err != nil {
return nil, err
}
return buffer, nil
}
// Executes the template with the given context and writes to writer (io.Writer)
// on success. Context can be nil. Nothing is written on error; instead the error
// is being returned.
func (tpl *Template) ExecuteWriter(context Context, writer io.Writer) error {
buf, err := tpl.newBufferAndExecute(context)
if err != nil {
return err
}
_, err = buf.WriteTo(writer)
if err != nil {
return err
}
return nil
}
// Same as ExecuteWriter. The only difference between both functions is that
// this function might already have written parts of the generated template in the
// case of an execution error because there's no intermediate buffer involved for
// performance reasons. This is handy if you need high performance template
// generation or if you want to manage your own pool of buffers.
func (tpl *Template) ExecuteWriterUnbuffered(context Context, writer io.Writer) error {
return tpl.newTemplateWriterAndExecute(context, writer)
}
// Executes the template and returns the rendered template as a []byte
func (tpl *Template) ExecuteBytes(context Context) ([]byte, error) {
// Execute template
buffer, err := tpl.newBufferAndExecute(context)
if err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
// Executes the template and returns the rendered template as a string
func (tpl *Template) Execute(context Context) (string, error) {
// Execute template
buffer, err := tpl.newBufferAndExecute(context)
if err != nil {
return "", err
}
return buffer.String(), nil
}
func (tpl *Template) ExecuteBlocks(context Context, blocks []string) (map[string]string, error) {
var parents []*Template
result := make(map[string]string)
parent := tpl
for parent != nil {
parents = append(parents, parent)
parent = parent.parent
}
for _, t := range parents {
buffer := bytes.NewBuffer(make([]byte, 0, int(float64(t.size)*1.3)))
_, ctx, err := t.newContextForExecution(context)
if err != nil {
return nil, err
}
for _, blockName := range blocks {
if _, ok := result[blockName]; ok {
continue
}
if blockWrapper, ok := t.blocks[blockName]; ok {
bErr := blockWrapper.Execute(ctx, buffer)
if bErr != nil {
return nil, bErr
}
result[blockName] = buffer.String()
buffer.Reset()
}
}
// We have found all blocks
if len(blocks) == len(result) {
break
}
}
return result, nil
}

157
vendor/github.com/flosch/pongo2/template_loader.go generated vendored Normal file
View File

@ -0,0 +1,157 @@
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 = ""
}()
}
*/

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

@ -0,0 +1,305 @@
package pongo2
import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"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).
type TemplateSet struct {
name string
loaders []TemplateLoader
// Globals will be provided to all templates created within this template set
Globals Context
// If debug is true (default false), ExecutionContext.Logf() will work and output
// to STDOUT. Furthermore, FromCache() won't cache the templates.
// Make sure to synchronize the access to it in case you're changing this
// variable during program execution (and template compilation/execution).
Debug bool
// Options allow you to change the behavior of template-engine.
// You can change the options before calling the Execute method.
Options *Options
// Sandbox features
// - Disallow access to specific tags and/or filters (using BanTag() and BanFilter())
//
// For efficiency reasons you can ban tags/filters only *before* you have
// added your first template to the set (restrictions are statically checked).
// After you added one, it's not possible anymore (for your personal security).
firstTemplateCreated bool
bannedTags map[string]bool
bannedFilters map[string]bool
// Template cache (for FromCache())
templateCache map[string]*Template
templateCacheMutex sync.Mutex
}
// NewSet can be used to create sets with different kind of templates
// (e. g. web from mail templates), with different globals or
// other configurations.
func NewSet(name string, loaders ...TemplateLoader) *TemplateSet {
if len(loaders) == 0 {
panic(fmt.Errorf("at least one template loader must be specified"))
}
return &TemplateSet{
name: name,
loaders: loaders,
Globals: make(Context),
bannedTags: make(map[string]bool),
bannedFilters: make(map[string]bool),
templateCache: make(map[string]*Template),
Options: newOptions(),
}
}
func (set *TemplateSet) AddLoader(loaders ...TemplateLoader) {
set.loaders = append(set.loaders, loaders...)
}
func (set *TemplateSet) resolveFilename(tpl *Template, path string) string {
return set.resolveFilenameForLoader(set.loaders[0], tpl, path)
}
func (set *TemplateSet) resolveFilenameForLoader(loader TemplateLoader, tpl *Template, path string) string {
name := ""
if tpl != nil && tpl.isTplString {
return path
}
if tpl != nil {
name = tpl.name
}
return loader.Abs(name, path)
}
// BanTag bans a specific tag for this template set. See more in the documentation for TemplateSet.
func (set *TemplateSet) BanTag(name string) error {
_, has := tags[name]
if !has {
return errors.Errorf("tag '%s' not found", name)
}
if set.firstTemplateCreated {
return errors.New("you cannot ban any tags after you've added your first template to your template set")
}
_, has = set.bannedTags[name]
if has {
return errors.Errorf("tag '%s' is already banned", name)
}
set.bannedTags[name] = true
return nil
}
// BanFilter bans a specific filter for this template set. See more in the documentation for TemplateSet.
func (set *TemplateSet) BanFilter(name string) error {
_, has := filters[name]
if !has {
return errors.Errorf("filter '%s' not found", name)
}
if set.firstTemplateCreated {
return errors.New("you cannot ban any filters after you've added your first template to your template set")
}
_, has = set.bannedFilters[name]
if has {
return errors.Errorf("filter '%s' is already banned", name)
}
set.bannedFilters[name] = true
return nil
}
func (set *TemplateSet) resolveTemplate(tpl *Template, path string) (name string, loader TemplateLoader, fd io.Reader, err error) {
// iterate over loaders until we appear to have a valid template
for _, loader = range set.loaders {
name = set.resolveFilenameForLoader(loader, tpl, path)
fd, err = loader.Get(name)
if err == nil {
return
}
}
return path, nil, nil, fmt.Errorf("unable to resolve template")
}
// CleanCache cleans the template cache. If filenames is not empty,
// it will remove the template caches of those filenames.
// Or it will empty the whole template cache. It is thread-safe.
func (set *TemplateSet) CleanCache(filenames ...string) {
set.templateCacheMutex.Lock()
defer set.templateCacheMutex.Unlock()
if len(filenames) == 0 {
set.templateCache = make(map[string]*Template, len(set.templateCache))
}
for _, filename := range filenames {
delete(set.templateCache, set.resolveFilename(nil, filename))
}
}
// FromCache is a convenient method to cache templates. It is thread-safe
// and will only compile the template associated with a filename once.
// If TemplateSet.Debug is true (for example during development phase),
// FromCache() will not cache the template and instead recompile it on any
// call (to make changes to a template live instantaneously).
func (set *TemplateSet) FromCache(filename string) (*Template, error) {
if set.Debug {
// Recompile on any request
return set.FromFile(filename)
}
// Cache the template
cleanedFilename := set.resolveFilename(nil, filename)
set.templateCacheMutex.Lock()
defer set.templateCacheMutex.Unlock()
tpl, has := set.templateCache[cleanedFilename]
// Cache miss
if !has {
tpl, err := set.FromFile(cleanedFilename)
if err != nil {
return nil, err
}
set.templateCache[cleanedFilename] = tpl
return tpl, nil
}
// Cache hit
return tpl, nil
}
// FromString loads a template from string and returns a Template instance.
func (set *TemplateSet) FromString(tpl string) (*Template, error) {
set.firstTemplateCreated = true
return newTemplateString(set, []byte(tpl))
}
// FromBytes loads a template from bytes and returns a Template instance.
func (set *TemplateSet) FromBytes(tpl []byte) (*Template, error) {
set.firstTemplateCreated = true
return newTemplateString(set, tpl)
}
// FromFile loads a template from a filename and returns a Template instance.
func (set *TemplateSet) FromFile(filename string) (*Template, error) {
set.firstTemplateCreated = true
_, _, fd, err := set.resolveTemplate(nil, filename)
if err != nil {
return nil, &Error{
Filename: filename,
Sender: "fromfile",
OrigError: err,
}
}
buf, err := ioutil.ReadAll(fd)
if err != nil {
return nil, &Error{
Filename: filename,
Sender: "fromfile",
OrigError: err,
}
}
return newTemplate(set, filename, false, buf)
}
// RenderTemplateString is a shortcut and renders a template string directly.
func (set *TemplateSet) RenderTemplateString(s string, ctx Context) (string, error) {
set.firstTemplateCreated = true
tpl := Must(set.FromString(s))
result, err := tpl.Execute(ctx)
if err != nil {
return "", err
}
return result, nil
}
// RenderTemplateBytes is a shortcut and renders template bytes directly.
func (set *TemplateSet) RenderTemplateBytes(b []byte, ctx Context) (string, error) {
set.firstTemplateCreated = true
tpl := Must(set.FromBytes(b))
result, err := tpl.Execute(ctx)
if err != nil {
return "", err
}
return result, nil
}
// RenderTemplateFile is a shortcut and renders a template file directly.
func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) (string, error) {
set.firstTemplateCreated = true
tpl := Must(set.FromFile(fn))
result, err := tpl.Execute(ctx)
if err != nil {
return "", err
}
return result, nil
}
func (set *TemplateSet) logf(format string, args ...interface{}) {
if set.Debug {
logger.Printf(fmt.Sprintf("[template set: %s] %s", set.name, format), args...)
}
}
// Logging function (internally used)
func logf(format string, items ...interface{}) {
if debug {
logger.Printf(format, items...)
}
}
var (
debug bool // internal debugging
logger = log.New(os.Stdout, "[pongo2] ", log.LstdFlags|log.Lshortfile)
// DefaultLoader allows the default un-sandboxed access to the local file
// system and is being used by the DefaultSet.
DefaultLoader = MustNewLocalFileSystemLoader("")
// DefaultSet is a set created for you for convinience reasons.
DefaultSet = NewSet("default", DefaultLoader)
// Methods on the default set
FromString = DefaultSet.FromString
FromBytes = DefaultSet.FromBytes
FromFile = DefaultSet.FromFile
FromCache = DefaultSet.FromCache
RenderTemplateString = DefaultSet.RenderTemplateString
RenderTemplateFile = DefaultSet.RenderTemplateFile
// Globals for the default set
Globals = DefaultSet.Globals
)

520
vendor/github.com/flosch/pongo2/value.go generated vendored Normal file
View File

@ -0,0 +1,520 @@
package pongo2
import (
"fmt"
"reflect"
"sort"
"strconv"
"strings"
)
type Value struct {
val reflect.Value
safe bool // used to indicate whether a Value needs explicit escaping in the template
}
// AsValue converts any given value to a pongo2.Value
// Usually being used within own functions passed to a template
// through a Context or within filter functions.
//
// Example:
// AsValue("my string")
func AsValue(i interface{}) *Value {
return &Value{
val: reflect.ValueOf(i),
}
}
// AsSafeValue works like AsValue, but does not apply the 'escape' filter.
func AsSafeValue(i interface{}) *Value {
return &Value{
val: reflect.ValueOf(i),
safe: true,
}
}
func (v *Value) getResolvedValue() reflect.Value {
if v.val.IsValid() && v.val.Kind() == reflect.Ptr {
return v.val.Elem()
}
return v.val
}
// IsString checks whether the underlying value is a string
func (v *Value) IsString() bool {
return v.getResolvedValue().Kind() == reflect.String
}
// IsBool checks whether the underlying value is a bool
func (v *Value) IsBool() bool {
return v.getResolvedValue().Kind() == reflect.Bool
}
// IsFloat checks whether the underlying value is a float
func (v *Value) IsFloat() bool {
return v.getResolvedValue().Kind() == reflect.Float32 ||
v.getResolvedValue().Kind() == reflect.Float64
}
// IsInteger checks whether the underlying value is an integer
func (v *Value) IsInteger() bool {
return v.getResolvedValue().Kind() == reflect.Int ||
v.getResolvedValue().Kind() == reflect.Int8 ||
v.getResolvedValue().Kind() == reflect.Int16 ||
v.getResolvedValue().Kind() == reflect.Int32 ||
v.getResolvedValue().Kind() == reflect.Int64 ||
v.getResolvedValue().Kind() == reflect.Uint ||
v.getResolvedValue().Kind() == reflect.Uint8 ||
v.getResolvedValue().Kind() == reflect.Uint16 ||
v.getResolvedValue().Kind() == reflect.Uint32 ||
v.getResolvedValue().Kind() == reflect.Uint64
}
// IsNumber checks whether the underlying value is either an integer
// or a float.
func (v *Value) IsNumber() bool {
return v.IsInteger() || v.IsFloat()
}
// IsNil checks whether the underlying value is NIL
func (v *Value) IsNil() bool {
//fmt.Printf("%+v\n", v.getResolvedValue().Type().String())
return !v.getResolvedValue().IsValid()
}
// String returns a string for the underlying value. If this value is not
// of type string, pongo2 tries to convert it. Currently the following
// types for underlying values are supported:
//
// 1. string
// 2. int/uint (any size)
// 3. float (any precision)
// 4. bool
// 5. time.Time
// 6. String() will be called on the underlying value if provided
//
// NIL values will lead to an empty string. Unsupported types are leading
// to their respective type name.
func (v *Value) String() string {
if v.IsNil() {
return ""
}
switch v.getResolvedValue().Kind() {
case reflect.String:
return v.getResolvedValue().String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(v.getResolvedValue().Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(v.getResolvedValue().Uint(), 10)
case reflect.Float32, reflect.Float64:
return fmt.Sprintf("%f", v.getResolvedValue().Float())
case reflect.Bool:
if v.Bool() {
return "True"
}
return "False"
case reflect.Struct:
if t, ok := v.Interface().(fmt.Stringer); ok {
return t.String()
}
}
logf("Value.String() not implemented for type: %s\n", v.getResolvedValue().Kind().String())
return v.getResolvedValue().String()
}
// Integer returns the underlying value as an integer (converts the underlying
// value, if necessary). If it's not possible to convert the underlying value,
// it will return 0.
func (v *Value) Integer() int {
switch v.getResolvedValue().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return int(v.getResolvedValue().Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return int(v.getResolvedValue().Uint())
case reflect.Float32, reflect.Float64:
return int(v.getResolvedValue().Float())
case reflect.String:
// Try to convert from string to int (base 10)
f, err := strconv.ParseFloat(v.getResolvedValue().String(), 64)
if err != nil {
return 0
}
return int(f)
default:
logf("Value.Integer() not available for type: %s\n", v.getResolvedValue().Kind().String())
return 0
}
}
// Float returns the underlying value as a float (converts the underlying
// value, if necessary). If it's not possible to convert the underlying value,
// it will return 0.0.
func (v *Value) Float() float64 {
switch v.getResolvedValue().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return float64(v.getResolvedValue().Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return float64(v.getResolvedValue().Uint())
case reflect.Float32, reflect.Float64:
return v.getResolvedValue().Float()
case reflect.String:
// Try to convert from string to float64 (base 10)
f, err := strconv.ParseFloat(v.getResolvedValue().String(), 64)
if err != nil {
return 0.0
}
return f
default:
logf("Value.Float() not available for type: %s\n", v.getResolvedValue().Kind().String())
return 0.0
}
}
// Bool returns the underlying value as bool. If the value is not bool, false
// will always be returned. If you're looking for true/false-evaluation of the
// underlying value, have a look on the IsTrue()-function.
func (v *Value) Bool() bool {
switch v.getResolvedValue().Kind() {
case reflect.Bool:
return v.getResolvedValue().Bool()
default:
logf("Value.Bool() not available for type: %s\n", v.getResolvedValue().Kind().String())
return false
}
}
// IsTrue tries to evaluate the underlying value the Pythonic-way:
//
// Returns TRUE in one the following cases:
//
// * int != 0
// * uint != 0
// * float != 0.0
// * len(array/chan/map/slice/string) > 0
// * bool == true
// * underlying value is a struct
//
// Otherwise returns always FALSE.
func (v *Value) IsTrue() bool {
switch v.getResolvedValue().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.getResolvedValue().Int() != 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return v.getResolvedValue().Uint() != 0
case reflect.Float32, reflect.Float64:
return v.getResolvedValue().Float() != 0
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
return v.getResolvedValue().Len() > 0
case reflect.Bool:
return v.getResolvedValue().Bool()
case reflect.Struct:
return true // struct instance is always true
default:
logf("Value.IsTrue() not available for type: %s\n", v.getResolvedValue().Kind().String())
return false
}
}
// Negate tries to negate the underlying value. It's mainly used for
// the NOT-operator and in conjunction with a call to
// return_value.IsTrue() afterwards.
//
// Example:
// AsValue(1).Negate().IsTrue() == false
func (v *Value) Negate() *Value {
switch v.getResolvedValue().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if v.Integer() != 0 {
return AsValue(0)
}
return AsValue(1)
case reflect.Float32, reflect.Float64:
if v.Float() != 0.0 {
return AsValue(float64(0.0))
}
return AsValue(float64(1.1))
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
return AsValue(v.getResolvedValue().Len() == 0)
case reflect.Bool:
return AsValue(!v.getResolvedValue().Bool())
case reflect.Struct:
return AsValue(false)
default:
logf("Value.IsTrue() not available for type: %s\n", v.getResolvedValue().Kind().String())
return AsValue(true)
}
}
// Len returns the length for an array, chan, map, slice or string.
// Otherwise it will return 0.
func (v *Value) Len() int {
switch v.getResolvedValue().Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
return v.getResolvedValue().Len()
case reflect.String:
runes := []rune(v.getResolvedValue().String())
return len(runes)
default:
logf("Value.Len() not available for type: %s\n", v.getResolvedValue().Kind().String())
return 0
}
}
// Slice slices an array, slice or string. Otherwise it will
// return an empty []int.
func (v *Value) Slice(i, j int) *Value {
switch v.getResolvedValue().Kind() {
case reflect.Array, reflect.Slice:
return AsValue(v.getResolvedValue().Slice(i, j).Interface())
case reflect.String:
runes := []rune(v.getResolvedValue().String())
return AsValue(string(runes[i:j]))
default:
logf("Value.Slice() not available for type: %s\n", v.getResolvedValue().Kind().String())
return AsValue([]int{})
}
}
// Index gets the i-th item of an array, slice or string. Otherwise
// it will return NIL.
func (v *Value) Index(i int) *Value {
switch v.getResolvedValue().Kind() {
case reflect.Array, reflect.Slice:
if i >= v.Len() {
return AsValue(nil)
}
return AsValue(v.getResolvedValue().Index(i).Interface())
case reflect.String:
//return AsValue(v.getResolvedValue().Slice(i, i+1).Interface())
s := v.getResolvedValue().String()
runes := []rune(s)
if i < len(runes) {
return AsValue(string(runes[i]))
}
return AsValue("")
default:
logf("Value.Slice() not available for type: %s\n", v.getResolvedValue().Kind().String())
return AsValue([]int{})
}
}
// Contains checks whether the underlying value (which must be of type struct, map,
// string, array or slice) contains of another Value (e. g. used to check
// whether a struct contains of a specific field or a map contains a specific key).
//
// Example:
// AsValue("Hello, World!").Contains(AsValue("World")) == true
func (v *Value) Contains(other *Value) bool {
switch v.getResolvedValue().Kind() {
case reflect.Struct:
fieldValue := v.getResolvedValue().FieldByName(other.String())
return fieldValue.IsValid()
case reflect.Map:
var mapValue reflect.Value
switch other.Interface().(type) {
case int:
mapValue = v.getResolvedValue().MapIndex(other.getResolvedValue())
case string:
mapValue = v.getResolvedValue().MapIndex(other.getResolvedValue())
default:
logf("Value.Contains() does not support lookup type '%s'\n", other.getResolvedValue().Kind().String())
return false
}
return mapValue.IsValid()
case reflect.String:
return strings.Contains(v.getResolvedValue().String(), other.String())
case reflect.Slice, reflect.Array:
for i := 0; i < v.getResolvedValue().Len(); i++ {
item := v.getResolvedValue().Index(i)
if other.Interface() == item.Interface() {
return true
}
}
return false
default:
logf("Value.Contains() not available for type: %s\n", v.getResolvedValue().Kind().String())
return false
}
}
// CanSlice checks whether the underlying value is of type array, slice or string.
// You normally would use CanSlice() before using the Slice() operation.
func (v *Value) CanSlice() bool {
switch v.getResolvedValue().Kind() {
case reflect.Array, reflect.Slice, reflect.String:
return true
}
return false
}
// Iterate iterates over a map, array, slice or a string. It calls the
// function's first argument for every value with the following arguments:
//
// idx current 0-index
// count total amount of items
// key *Value for the key or item
// value *Value (only for maps, the respective value for a specific key)
//
// If the underlying value has no items or is not one of the types above,
// the empty function (function's second argument) will be called.
func (v *Value) Iterate(fn func(idx, count int, key, value *Value) bool, empty func()) {
v.IterateOrder(fn, empty, false, false)
}
// IterateOrder behaves like Value.Iterate, but can iterate through an array/slice/string in reverse. Does
// not affect the iteration through a map because maps don't have any particular order.
// However, you can force an order using the `sorted` keyword (and even use `reversed sorted`).
func (v *Value) IterateOrder(fn func(idx, count int, key, value *Value) bool, empty func(), reverse bool, sorted bool) {
switch v.getResolvedValue().Kind() {
case reflect.Map:
keys := sortedKeys(v.getResolvedValue().MapKeys())
if sorted {
if reverse {
sort.Sort(sort.Reverse(keys))
} else {
sort.Sort(keys)
}
}
keyLen := len(keys)
for idx, key := range keys {
value := v.getResolvedValue().MapIndex(key)
if !fn(idx, keyLen, &Value{val: key}, &Value{val: value}) {
return
}
}
if keyLen == 0 {
empty()
}
return // done
case reflect.Array, reflect.Slice:
var items valuesList
itemCount := v.getResolvedValue().Len()
for i := 0; i < itemCount; i++ {
items = append(items, &Value{val: v.getResolvedValue().Index(i)})
}
if sorted {
if reverse {
sort.Sort(sort.Reverse(items))
} else {
sort.Sort(items)
}
} else {
if reverse {
for i := 0; i < itemCount/2; i++ {
items[i], items[itemCount-1-i] = items[itemCount-1-i], items[i]
}
}
}
if len(items) > 0 {
for idx, item := range items {
if !fn(idx, itemCount, item, nil) {
return
}
}
} else {
empty()
}
return // done
case reflect.String:
if sorted {
// TODO(flosch): Handle sorted
panic("TODO: handle sort for type string")
}
// TODO(flosch): Not utf8-compatible (utf8-decoding necessary)
charCount := v.getResolvedValue().Len()
if charCount > 0 {
if reverse {
for i := charCount - 1; i >= 0; i-- {
if !fn(i, charCount, &Value{val: v.getResolvedValue().Slice(i, i+1)}, nil) {
return
}
}
} else {
for i := 0; i < charCount; i++ {
if !fn(i, charCount, &Value{val: v.getResolvedValue().Slice(i, i+1)}, nil) {
return
}
}
}
} else {
empty()
}
return // done
default:
logf("Value.Iterate() not available for type: %s\n", v.getResolvedValue().Kind().String())
}
empty()
}
// Interface gives you access to the underlying value.
func (v *Value) Interface() interface{} {
if v.val.IsValid() {
return v.val.Interface()
}
return nil
}
// EqualValueTo checks whether two values are containing the same value or object.
func (v *Value) EqualValueTo(other *Value) bool {
// comparison of uint with int fails using .Interface()-comparison (see issue #64)
if v.IsInteger() && other.IsInteger() {
return v.Integer() == other.Integer()
}
return v.Interface() == other.Interface()
}
type sortedKeys []reflect.Value
func (sk sortedKeys) Len() int {
return len(sk)
}
func (sk sortedKeys) Less(i, j int) bool {
vi := &Value{val: sk[i]}
vj := &Value{val: sk[j]}
switch {
case vi.IsInteger() && vj.IsInteger():
return vi.Integer() < vj.Integer()
case vi.IsFloat() && vj.IsFloat():
return vi.Float() < vj.Float()
default:
return vi.String() < vj.String()
}
}
func (sk sortedKeys) Swap(i, j int) {
sk[i], sk[j] = sk[j], sk[i]
}
type valuesList []*Value
func (vl valuesList) Len() int {
return len(vl)
}
func (vl valuesList) Less(i, j int) bool {
vi := vl[i]
vj := vl[j]
switch {
case vi.IsInteger() && vj.IsInteger():
return vi.Integer() < vj.Integer()
case vi.IsFloat() && vj.IsFloat():
return vi.Float() < vj.Float()
default:
return vi.String() < vj.String()
}
}
func (vl valuesList) Swap(i, j int) {
vl[i], vl[j] = vl[j], vl[i]
}

695
vendor/github.com/flosch/pongo2/variable.go generated vendored Normal file
View File

@ -0,0 +1,695 @@
package pongo2
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/juju/errors"
)
const (
varTypeInt = iota
varTypeIdent
)
var (
typeOfValuePtr = reflect.TypeOf(new(Value))
typeOfExecCtxPtr = reflect.TypeOf(new(ExecutionContext))
)
type variablePart struct {
typ int
s string
i int
isFunctionCall bool
callingArgs []functionCallArgument // needed for a function call, represents all argument nodes (INode supports nested function calls)
}
type functionCallArgument interface {
Evaluate(*ExecutionContext) (*Value, *Error)
}
// TODO: Add location tokens
type stringResolver struct {
locationToken *Token
val string
}
type intResolver struct {
locationToken *Token
val int
}
type floatResolver struct {
locationToken *Token
val float64
}
type boolResolver struct {
locationToken *Token
val bool
}
type variableResolver struct {
locationToken *Token
parts []*variablePart
}
type nodeFilteredVariable struct {
locationToken *Token
resolver IEvaluator
filterChain []*filterCall
}
type nodeVariable struct {
locationToken *Token
expr IEvaluator
}
type executionCtxEval struct{}
func (v *nodeFilteredVariable) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := v.Evaluate(ctx)
if err != nil {
return err
}
writer.WriteString(value.String())
return nil
}
func (vr *variableResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := vr.Evaluate(ctx)
if err != nil {
return err
}
writer.WriteString(value.String())
return nil
}
func (s *stringResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := s.Evaluate(ctx)
if err != nil {
return err
}
writer.WriteString(value.String())
return nil
}
func (i *intResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := i.Evaluate(ctx)
if err != nil {
return err
}
writer.WriteString(value.String())
return nil
}
func (f *floatResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := f.Evaluate(ctx)
if err != nil {
return err
}
writer.WriteString(value.String())
return nil
}
func (b *boolResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := b.Evaluate(ctx)
if err != nil {
return err
}
writer.WriteString(value.String())
return nil
}
func (v *nodeFilteredVariable) GetPositionToken() *Token {
return v.locationToken
}
func (vr *variableResolver) GetPositionToken() *Token {
return vr.locationToken
}
func (s *stringResolver) GetPositionToken() *Token {
return s.locationToken
}
func (i *intResolver) GetPositionToken() *Token {
return i.locationToken
}
func (f *floatResolver) GetPositionToken() *Token {
return f.locationToken
}
func (b *boolResolver) GetPositionToken() *Token {
return b.locationToken
}
func (s *stringResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
return AsValue(s.val), nil
}
func (i *intResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
return AsValue(i.val), nil
}
func (f *floatResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
return AsValue(f.val), nil
}
func (b *boolResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
return AsValue(b.val), nil
}
func (s *stringResolver) FilterApplied(name string) bool {
return false
}
func (i *intResolver) FilterApplied(name string) bool {
return false
}
func (f *floatResolver) FilterApplied(name string) bool {
return false
}
func (b *boolResolver) FilterApplied(name string) bool {
return false
}
func (nv *nodeVariable) FilterApplied(name string) bool {
return nv.expr.FilterApplied(name)
}
func (nv *nodeVariable) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
value, err := nv.expr.Evaluate(ctx)
if err != nil {
return err
}
if !nv.expr.FilterApplied("safe") && !value.safe && value.IsString() && ctx.Autoescape {
// apply escape filter
value, err = filters["escape"](value, nil)
if err != nil {
return err
}
}
writer.WriteString(value.String())
return nil
}
func (executionCtxEval) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
return AsValue(ctx), nil
}
func (vr *variableResolver) FilterApplied(name string) bool {
return false
}
func (vr *variableResolver) String() string {
parts := make([]string, 0, len(vr.parts))
for _, p := range vr.parts {
switch p.typ {
case varTypeInt:
parts = append(parts, strconv.Itoa(p.i))
case varTypeIdent:
parts = append(parts, p.s)
default:
panic("unimplemented")
}
}
return strings.Join(parts, ".")
}
func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) {
var current reflect.Value
var isSafe bool
for idx, part := range vr.parts {
if idx == 0 {
// We're looking up the first part of the variable.
// First we're having a look in our private
// context (e. g. information provided by tags, like the forloop)
val, inPrivate := ctx.Private[vr.parts[0].s]
if !inPrivate {
// Nothing found? Then have a final lookup in the public context
val = ctx.Public[vr.parts[0].s]
}
current = reflect.ValueOf(val) // Get the initial value
} else {
// Next parts, resolve it from current
// Before resolving the pointer, let's see if we have a method to call
// Problem with resolving the pointer is we're changing the receiver
isFunc := false
if part.typ == varTypeIdent {
funcValue := current.MethodByName(part.s)
if funcValue.IsValid() {
current = funcValue
isFunc = true
}
}
if !isFunc {
// If current a pointer, resolve it
if current.Kind() == reflect.Ptr {
current = current.Elem()
if !current.IsValid() {
// Value is not valid (anymore)
return AsValue(nil), nil
}
}
// Look up which part must be called now
switch part.typ {
case varTypeInt:
// Calling an index is only possible for:
// * slices/arrays/strings
switch current.Kind() {
case reflect.String, reflect.Array, reflect.Slice:
if part.i >= 0 && current.Len() > part.i {
current = current.Index(part.i)
} else {
// In Django, exceeding the length of a list is just empty.
return AsValue(nil), nil
}
default:
return nil, errors.Errorf("Can't access an index on type %s (variable %s)",
current.Kind().String(), vr.String())
}
case varTypeIdent:
// debugging:
// fmt.Printf("now = %s (kind: %s)\n", part.s, current.Kind().String())
// Calling a field or key
switch current.Kind() {
case reflect.Struct:
current = current.FieldByName(part.s)
case reflect.Map:
current = current.MapIndex(reflect.ValueOf(part.s))
default:
return nil, errors.Errorf("Can't access a field by name on type %s (variable %s)",
current.Kind().String(), vr.String())
}
default:
panic("unimplemented")
}
}
}
if !current.IsValid() {
// Value is not valid (anymore)
return AsValue(nil), nil
}
// If current is a reflect.ValueOf(pongo2.Value), then unpack it
// Happens in function calls (as a return value) or by injecting
// into the execution context (e.g. in a for-loop)
if current.Type() == typeOfValuePtr {
tmpValue := current.Interface().(*Value)
current = tmpValue.val
isSafe = tmpValue.safe
}
// Check whether this is an interface and resolve it where required
if current.Kind() == reflect.Interface {
current = reflect.ValueOf(current.Interface())
}
// Check if the part is a function call
if part.isFunctionCall || current.Kind() == reflect.Func {
// Check for callable
if current.Kind() != reflect.Func {
return nil, errors.Errorf("'%s' is not a function (it is %s)", vr.String(), current.Kind().String())
}
// Check for correct function syntax and types
// func(*Value, ...) *Value
t := current.Type()
currArgs := part.callingArgs
// If an implicit ExecCtx is needed
if t.NumIn() > 0 && t.In(0) == typeOfExecCtxPtr {
currArgs = append([]functionCallArgument{executionCtxEval{}}, currArgs...)
}
// Input arguments
if len(currArgs) != t.NumIn() && !(len(currArgs) >= t.NumIn()-1 && t.IsVariadic()) {
return nil,
errors.Errorf("Function input argument count (%d) of '%s' must be equal to the calling argument count (%d).",
t.NumIn(), vr.String(), len(currArgs))
}
// Output arguments
if t.NumOut() != 1 && t.NumOut() != 2 {
return nil, errors.Errorf("'%s' must have exactly 1 or 2 output arguments, the second argument must be of type error", vr.String())
}
// Evaluate all parameters
var parameters []reflect.Value
numArgs := t.NumIn()
isVariadic := t.IsVariadic()
var fnArg reflect.Type
for idx, arg := range currArgs {
pv, err := arg.Evaluate(ctx)
if err != nil {
return nil, err
}
if isVariadic {
if idx >= t.NumIn()-1 {
fnArg = t.In(numArgs - 1).Elem()
} else {
fnArg = t.In(idx)
}
} else {
fnArg = t.In(idx)
}
if fnArg != typeOfValuePtr {
// Function's argument is not a *pongo2.Value, then we have to check whether input argument is of the same type as the function's argument
if !isVariadic {
if fnArg != reflect.TypeOf(pv.Interface()) && fnArg.Kind() != reflect.Interface {
return nil, errors.Errorf("Function input argument %d of '%s' must be of type %s or *pongo2.Value (not %T).",
idx, vr.String(), fnArg.String(), pv.Interface())
}
// Function's argument has another type, using the interface-value
parameters = append(parameters, reflect.ValueOf(pv.Interface()))
} else {
if fnArg != reflect.TypeOf(pv.Interface()) && fnArg.Kind() != reflect.Interface {
return nil, errors.Errorf("Function variadic input argument of '%s' must be of type %s or *pongo2.Value (not %T).",
vr.String(), fnArg.String(), pv.Interface())
}
// Function's argument has another type, using the interface-value
parameters = append(parameters, reflect.ValueOf(pv.Interface()))
}
} else {
// Function's argument is a *pongo2.Value
parameters = append(parameters, reflect.ValueOf(pv))
}
}
// Check if any of the values are invalid
for _, p := range parameters {
if p.Kind() == reflect.Invalid {
return nil, errors.Errorf("Calling a function using an invalid parameter")
}
}
// Call it and get first return parameter back
values := current.Call(parameters)
rv := values[0]
if t.NumOut() == 2 {
e := values[1].Interface()
if e != nil {
err, ok := e.(error)
if !ok {
return nil, errors.Errorf("The second return value is not an error")
}
if err != nil {
return nil, err
}
}
}
if rv.Type() != typeOfValuePtr {
current = reflect.ValueOf(rv.Interface())
} else {
// Return the function call value
current = rv.Interface().(*Value).val
isSafe = rv.Interface().(*Value).safe
}
}
if !current.IsValid() {
// Value is not valid (e. g. NIL value)
return AsValue(nil), nil
}
}
return &Value{val: current, safe: isSafe}, nil
}
func (vr *variableResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
value, err := vr.resolve(ctx)
if err != nil {
return AsValue(nil), ctx.Error(err.Error(), vr.locationToken)
}
return value, nil
}
func (v *nodeFilteredVariable) FilterApplied(name string) bool {
for _, filter := range v.filterChain {
if filter.name == name {
return true
}
}
return false
}
func (v *nodeFilteredVariable) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
value, err := v.resolver.Evaluate(ctx)
if err != nil {
return nil, err
}
for _, filter := range v.filterChain {
value, err = filter.Execute(value, ctx)
if err != nil {
return nil, err
}
}
return value, nil
}
// IDENT | IDENT.(IDENT|NUMBER)...
func (p *Parser) parseVariableOrLiteral() (IEvaluator, *Error) {
t := p.Current()
if t == nil {
return nil, p.Error("Unexpected EOF, expected a number, string, keyword or identifier.", p.lastToken)
}
// Is first part a number or a string, there's nothing to resolve (because there's only to return the value then)
switch t.Typ {
case TokenNumber:
p.Consume()
// One exception to the rule that we don't have float64 literals is at the beginning
// of an expression (or a variable name). Since we know we started with an integer
// which can't obviously be a variable name, we can check whether the first number
// is followed by dot (and then a number again). If so we're converting it to a float64.
if p.Match(TokenSymbol, ".") != nil {
// float64
t2 := p.MatchType(TokenNumber)
if t2 == nil {
return nil, p.Error("Expected a number after the '.'.", nil)
}
f, err := strconv.ParseFloat(fmt.Sprintf("%s.%s", t.Val, t2.Val), 64)
if err != nil {
return nil, p.Error(err.Error(), t)
}
fr := &floatResolver{
locationToken: t,
val: f,
}
return fr, nil
}
i, err := strconv.Atoi(t.Val)
if err != nil {
return nil, p.Error(err.Error(), t)
}
nr := &intResolver{
locationToken: t,
val: i,
}
return nr, nil
case TokenString:
p.Consume()
sr := &stringResolver{
locationToken: t,
val: t.Val,
}
return sr, nil
case TokenKeyword:
p.Consume()
switch t.Val {
case "true":
br := &boolResolver{
locationToken: t,
val: true,
}
return br, nil
case "false":
br := &boolResolver{
locationToken: t,
val: false,
}
return br, nil
default:
return nil, p.Error("This keyword is not allowed here.", nil)
}
}
resolver := &variableResolver{
locationToken: t,
}
// First part of a variable MUST be an identifier
if t.Typ != TokenIdentifier {
return nil, p.Error("Expected either a number, string, keyword or identifier.", t)
}
resolver.parts = append(resolver.parts, &variablePart{
typ: varTypeIdent,
s: t.Val,
})
p.Consume() // we consumed the first identifier of the variable name
variableLoop:
for p.Remaining() > 0 {
t = p.Current()
if p.Match(TokenSymbol, ".") != nil {
// Next variable part (can be either NUMBER or IDENT)
t2 := p.Current()
if t2 != nil {
switch t2.Typ {
case TokenIdentifier:
resolver.parts = append(resolver.parts, &variablePart{
typ: varTypeIdent,
s: t2.Val,
})
p.Consume() // consume: IDENT
continue variableLoop
case TokenNumber:
i, err := strconv.Atoi(t2.Val)
if err != nil {
return nil, p.Error(err.Error(), t2)
}
resolver.parts = append(resolver.parts, &variablePart{
typ: varTypeInt,
i: i,
})
p.Consume() // consume: NUMBER
continue variableLoop
default:
return nil, p.Error("This token is not allowed within a variable name.", t2)
}
} else {
// EOF
return nil, p.Error("Unexpected EOF, expected either IDENTIFIER or NUMBER after DOT.",
p.lastToken)
}
} else if p.Match(TokenSymbol, "(") != nil {
// Function call
// FunctionName '(' Comma-separated list of expressions ')'
part := resolver.parts[len(resolver.parts)-1]
part.isFunctionCall = true
argumentLoop:
for {
if p.Remaining() == 0 {
return nil, p.Error("Unexpected EOF, expected function call argument list.", p.lastToken)
}
if p.Peek(TokenSymbol, ")") == nil {
// No closing bracket, so we're parsing an expression
exprArg, err := p.ParseExpression()
if err != nil {
return nil, err
}
part.callingArgs = append(part.callingArgs, exprArg)
if p.Match(TokenSymbol, ")") != nil {
// If there's a closing bracket after an expression, we will stop parsing the arguments
break argumentLoop
} else {
// If there's NO closing bracket, there MUST be an comma
if p.Match(TokenSymbol, ",") == nil {
return nil, p.Error("Missing comma or closing bracket after argument.", nil)
}
}
} else {
// We got a closing bracket, so stop parsing arguments
p.Consume()
break argumentLoop
}
}
// We're done parsing the function call, next variable part
continue variableLoop
}
// No dot or function call? Then we're done with the variable parsing
break
}
return resolver, nil
}
func (p *Parser) parseVariableOrLiteralWithFilter() (*nodeFilteredVariable, *Error) {
v := &nodeFilteredVariable{
locationToken: p.Current(),
}
// Parse the variable name
resolver, err := p.parseVariableOrLiteral()
if err != nil {
return nil, err
}
v.resolver = resolver
// Parse all the filters
filterLoop:
for p.Match(TokenSymbol, "|") != nil {
// Parse one single filter
filter, err := p.parseFilter()
if err != nil {
return nil, err
}
// Check sandbox filter restriction
if _, isBanned := p.template.set.bannedFilters[filter.name]; isBanned {
return nil, p.Error(fmt.Sprintf("Usage of filter '%s' is not allowed (sandbox restriction active).", filter.name), nil)
}
v.filterChain = append(v.filterChain, filter)
continue filterLoop
}
return v, nil
}
func (p *Parser) parseVariableElement() (INode, *Error) {
node := &nodeVariable{
locationToken: p.Current(),
}
p.Consume() // consume '{{'
expr, err := p.ParseExpression()
if err != nil {
return nil, err
}
node.expr = expr
if p.Match(TokenSymbol, "}}") == nil {
return nil, p.Error("'}}' expected", nil)
}
return node, nil
}

View File

@ -1,19 +0,0 @@
language: go
sudo: false
matrix:
include:
- go: 1.7.x
- go: 1.8.x
- go: 1.9.x
- go: 1.10.x
- go: 1.11.x
- go: tip
allow_failures:
- go: tip
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- go vet $(go list ./... | grep -v /vendor/)
- go test -v -race ./...

View File

@ -1,11 +1,11 @@
# Gorilla WebSocket
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
[![CircleCI](https://circleci.com/gh/gorilla/websocket.svg?style=svg)](https://circleci.com/gh/gorilla/websocket)
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket)
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
### Documentation
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
@ -27,7 +27,7 @@ package API is stable.
### Protocol Compliance
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn
Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
### Gorilla WebSocket compared with other packages
@ -40,7 +40,7 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn
</tr>
<tr>
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr>
<tr><td>Passes <a href="http://autobahn.ws/testsuite/">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
<tr><td>Passes <a href="https://github.com/crossbario/autobahn-testsuite">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr>
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr>
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>

View File

@ -70,7 +70,7 @@ type Dialer struct {
// HandshakeTimeout specifies the duration for the handshake to complete.
HandshakeTimeout time.Duration
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
// size is zero, then a useful default size is used. The I/O buffer sizes
// do not limit the size of the messages that can be sent or received.
ReadBufferSize, WriteBufferSize int
@ -140,7 +140,7 @@ var nilDialer = *DefaultDialer
// Use the response.Header to get the selected subprotocol
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
//
// The context will be used in the request and in the Dialer
// The context will be used in the request and in the Dialer.
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,

View File

@ -260,10 +260,12 @@ type Conn struct {
newCompressionWriter func(io.WriteCloser, int) io.WriteCloser
// Read fields
reader io.ReadCloser // the current reader returned to the application
readErr error
br *bufio.Reader
readRemaining int64 // bytes remaining in current frame.
reader io.ReadCloser // the current reader returned to the application
readErr error
br *bufio.Reader
// bytes remaining in current frame.
// set setReadRemaining to safely update this value and prevent overflow
readRemaining int64
readFinal bool // true the current message has more frames.
readLength int64 // Message size.
readLimit int64 // Maximum message size.
@ -320,6 +322,17 @@ func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int,
return c
}
// setReadRemaining tracks the number of bytes remaining on the connection. If n
// overflows, an ErrReadLimit is returned.
func (c *Conn) setReadRemaining(n int64) error {
if n < 0 {
return ErrReadLimit
}
c.readRemaining = n
return nil
}
// Subprotocol returns the negotiated protocol for the connection.
func (c *Conn) Subprotocol() string {
return c.subprotocol
@ -451,7 +464,8 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
return err
}
func (c *Conn) prepWrite(messageType int) error {
// beginMessage prepares a connection and message writer for a new message.
func (c *Conn) beginMessage(mw *messageWriter, messageType int) error {
// Close previous writer if not already closed by the application. It's
// probably better to return an error in this situation, but we cannot
// change this without breaking existing applications.
@ -471,6 +485,10 @@ func (c *Conn) prepWrite(messageType int) error {
return err
}
mw.c = c
mw.frameType = messageType
mw.pos = maxFrameHeaderSize
if c.writeBuf == nil {
wpd, ok := c.writePool.Get().(writePoolData)
if ok {
@ -491,16 +509,11 @@ func (c *Conn) prepWrite(messageType int) error {
// All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and
// PongMessage) are supported.
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
if err := c.prepWrite(messageType); err != nil {
var mw messageWriter
if err := c.beginMessage(&mw, messageType); err != nil {
return nil, err
}
mw := &messageWriter{
c: c,
frameType: messageType,
pos: maxFrameHeaderSize,
}
c.writer = mw
c.writer = &mw
if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) {
w := c.newCompressionWriter(c.writer, c.compressionLevel)
mw.compress = true
@ -517,10 +530,16 @@ type messageWriter struct {
err error
}
func (w *messageWriter) fatal(err error) error {
func (w *messageWriter) endMessage(err error) error {
if w.err != nil {
w.err = err
w.c.writer = nil
return err
}
c := w.c
w.err = err
c.writer = nil
if c.writePool != nil {
c.writePool.Put(writePoolData{buf: c.writeBuf})
c.writeBuf = nil
}
return err
}
@ -534,7 +553,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
// Check for invalid control frames.
if isControl(w.frameType) &&
(!final || length > maxControlFramePayloadSize) {
return w.fatal(errInvalidControlFrame)
return w.endMessage(errInvalidControlFrame)
}
b0 := byte(w.frameType)
@ -579,7 +598,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
copy(c.writeBuf[maxFrameHeaderSize-4:], key[:])
maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos])
if len(extra) > 0 {
return c.writeFatal(errors.New("websocket: internal error, extra used in client mode"))
return w.endMessage(c.writeFatal(errors.New("websocket: internal error, extra used in client mode")))
}
}
@ -600,15 +619,11 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
c.isWriting = false
if err != nil {
return w.fatal(err)
return w.endMessage(err)
}
if final {
c.writer = nil
if c.writePool != nil {
c.writePool.Put(writePoolData{buf: c.writeBuf})
c.writeBuf = nil
}
w.endMessage(errWriteClosed)
return nil
}
@ -706,11 +721,7 @@ func (w *messageWriter) Close() error {
if w.err != nil {
return w.err
}
if err := w.flushFrame(true, nil); err != nil {
return err
}
w.err = errWriteClosed
return nil
return w.flushFrame(true, nil)
}
// WritePreparedMessage writes prepared message into connection.
@ -742,10 +753,10 @@ func (c *Conn) WriteMessage(messageType int, data []byte) error {
if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) {
// Fast path with no allocations and single frame.
if err := c.prepWrite(messageType); err != nil {
var mw messageWriter
if err := c.beginMessage(&mw, messageType); err != nil {
return err
}
mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize}
n := copy(c.writeBuf[mw.pos:], data)
mw.pos += n
data = data[n:]
@ -792,7 +803,7 @@ func (c *Conn) advanceFrame() (int, error) {
final := p[0]&finalBit != 0
frameType := int(p[0] & 0xf)
mask := p[1]&maskBit != 0
c.readRemaining = int64(p[1] & 0x7f)
c.setReadRemaining(int64(p[1] & 0x7f))
c.readDecompress = false
if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 {
@ -826,7 +837,17 @@ func (c *Conn) advanceFrame() (int, error) {
return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType))
}
// 3. Read and parse frame length.
// 3. Read and parse frame length as per
// https://tools.ietf.org/html/rfc6455#section-5.2
//
// The length of the "Payload data", in bytes: if 0-125, that is the payload
// length.
// - If 126, the following 2 bytes interpreted as a 16-bit unsigned
// integer are the payload length.
// - If 127, the following 8 bytes interpreted as
// a 64-bit unsigned integer (the most significant bit MUST be 0) are the
// payload length. Multibyte length quantities are expressed in network byte
// order.
switch c.readRemaining {
case 126:
@ -834,13 +855,19 @@ func (c *Conn) advanceFrame() (int, error) {
if err != nil {
return noFrame, err
}
c.readRemaining = int64(binary.BigEndian.Uint16(p))
if err := c.setReadRemaining(int64(binary.BigEndian.Uint16(p))); err != nil {
return noFrame, err
}
case 127:
p, err := c.read(8)
if err != nil {
return noFrame, err
}
c.readRemaining = int64(binary.BigEndian.Uint64(p))
if err := c.setReadRemaining(int64(binary.BigEndian.Uint64(p))); err != nil {
return noFrame, err
}
}
// 4. Handle frame masking.
@ -863,6 +890,12 @@ func (c *Conn) advanceFrame() (int, error) {
if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage {
c.readLength += c.readRemaining
// Don't allow readLength to overflow in the presence of a large readRemaining
// counter.
if c.readLength < 0 {
return noFrame, ErrReadLimit
}
if c.readLimit > 0 && c.readLength > c.readLimit {
c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait))
return noFrame, ErrReadLimit
@ -876,7 +909,7 @@ func (c *Conn) advanceFrame() (int, error) {
var payload []byte
if c.readRemaining > 0 {
payload, err = c.read(int(c.readRemaining))
c.readRemaining = 0
c.setReadRemaining(0)
if err != nil {
return noFrame, err
}
@ -949,6 +982,7 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
c.readErr = hideTempErr(err)
break
}
if frameType == TextMessage || frameType == BinaryMessage {
c.messageReader = &messageReader{c}
c.reader = c.messageReader
@ -989,7 +1023,9 @@ func (r *messageReader) Read(b []byte) (int, error) {
if c.isServer {
c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n])
}
c.readRemaining -= int64(n)
rem := c.readRemaining
rem -= int64(n)
c.setReadRemaining(rem)
if c.readRemaining > 0 && c.readErr == io.EOF {
c.readErr = errUnexpectedEOF
}
@ -1041,7 +1077,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
// SetReadLimit sets the maximum size for a message read from the peer. If a
// SetReadLimit sets the maximum size in bytes for a message read from the peer. If a
// message exceeds the limit, the connection sends a close message to the peer
// and returns ErrReadLimit to the application.
func (c *Conn) SetReadLimit(limit int64) {

View File

@ -151,6 +151,53 @@
// checking. The application is responsible for checking the Origin header
// before calling the Upgrade function.
//
// Buffers
//
// Connections buffer network input and output to reduce the number
// of system calls when reading or writing messages.
//
// Write buffers are also used for constructing WebSocket frames. See RFC 6455,
// Section 5 for a discussion of message framing. A WebSocket frame header is
// written to the network each time a write buffer is flushed to the network.
// Decreasing the size of the write buffer can increase the amount of framing
// overhead on the connection.
//
// The buffer sizes in bytes are specified by the ReadBufferSize and
// WriteBufferSize fields in the Dialer and Upgrader. The Dialer uses a default
// size of 4096 when a buffer size field is set to zero. The Upgrader reuses
// buffers created by the HTTP server when a buffer size field is set to zero.
// The HTTP server buffers have a size of 4096 at the time of this writing.
//
// The buffer sizes do not limit the size of a message that can be read or
// written by a connection.
//
// Buffers are held for the lifetime of the connection by default. If the
// Dialer or Upgrader WriteBufferPool field is set, then a connection holds the
// write buffer only when writing a message.
//
// Applications should tune the buffer sizes to balance memory use and
// performance. Increasing the buffer size uses more memory, but can reduce the
// number of system calls to read or write the network. In the case of writing,
// increasing the buffer size can reduce the number of frame headers written to
// the network.
//
// Some guidelines for setting buffer parameters are:
//
// Limit the buffer sizes to the maximum expected message size. Buffers larger
// than the largest message do not provide any benefit.
//
// Depending on the distribution of message sizes, setting the buffer size to
// to a value less than the maximum expected message size can greatly reduce
// memory use with a small impact on performance. Here's an example: If 99% of
// the messages are smaller than 256 bytes and the maximum message size is 512
// bytes, then a buffer size of 256 bytes will result in 1.01 more system calls
// than a buffer size of 512 bytes. The memory savings is 50%.
//
// A write buffer pool is useful when the application has a modest number
// writes over a large number of connections. when buffers are pooled, a larger
// buffer size has a reduced impact on total memory use and has the benefit of
// reducing system calls and frame overhead.
//
// Compression EXPERIMENTAL
//
// Per message compression extensions (RFC 7692) are experimentally supported

3
vendor/github.com/gorilla/websocket/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/gorilla/websocket
go 1.12

2
vendor/github.com/gorilla/websocket/go.sum generated vendored Normal file
View File

@ -0,0 +1,2 @@
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=

42
vendor/github.com/gorilla/websocket/join.go generated vendored Normal file
View File

@ -0,0 +1,42 @@
// Copyright 2019 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"io"
"strings"
)
// JoinMessages concatenates received messages to create a single io.Reader.
// The string term is appended to each message. The returned reader does not
// support concurrent calls to the Read method.
func JoinMessages(c *Conn, term string) io.Reader {
return &joinReader{c: c, term: term}
}
type joinReader struct {
c *Conn
term string
r io.Reader
}
func (r *joinReader) Read(p []byte) (int, error) {
if r.r == nil {
var err error
_, r.r, err = r.c.NextReader()
if err != nil {
return 0, err
}
if r.term != "" {
r.r = io.MultiReader(r.r, strings.NewReader(r.term))
}
}
n, err := r.r.Read(p)
if err == io.EOF {
err = nil
r.r = nil
}
return n, err
}

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