mirror of
https://github.com/astaxie/beego.git
synced 2025-07-11 23:21:04 +00:00
Compare commits
215 Commits
Author | SHA1 | Date | |
---|---|---|---|
de5650b723 | |||
e5e4a3bea7 | |||
90d0b43f34 | |||
1b7f5ba2c4 | |||
96f01079cb | |||
9d4b5b313f | |||
b882009979 | |||
a768bf8f00 | |||
034599ca1d | |||
5a02c556b2 | |||
dc5c42e981 | |||
92a4119258 | |||
aa90c67a75 | |||
38a144c68f | |||
793047097c | |||
1923b8c767 | |||
fb640f0075 | |||
241f10b429 | |||
b8d626bbea | |||
2a6ceca861 | |||
10236b9f2d | |||
5a5482c77f | |||
11774c87a5 | |||
8395a26061 | |||
4348356d0a | |||
5620608418 | |||
de7ce2f9b0 | |||
d6c2a9fd4b | |||
582a4fa34b | |||
99647986de | |||
5bcde306ea | |||
2909ff3366 | |||
40078cba2c | |||
5d0c0a03d7 | |||
8cfd7f5c19 | |||
62d96c2e93 | |||
e844058aed | |||
1eab6bb32a | |||
c265d32c36 | |||
06692c3e27 | |||
394a73c75f | |||
cbcde8bd1f | |||
b17e49e6aa | |||
873f62edff | |||
cc0eacbe02 | |||
649c5c861d | |||
206a7ed1fc | |||
3c046a4dbf | |||
f2be6af2ca | |||
804b9769e0 | |||
585df01899 | |||
1a529c061c | |||
fcacfc08e3 | |||
a0ca3d61d6 | |||
39bd40e512 | |||
8748de95c7 | |||
0939e8e493 | |||
6a33feee46 | |||
58b2ac702c | |||
175714f69a | |||
d5b70118a3 | |||
a9629f707e | |||
6123c72752 | |||
56afa5c2bf | |||
0c576dac82 | |||
8462372c03 | |||
20ff97d53d | |||
8ce5b6cc52 | |||
afb787d49d | |||
0b165b78a1 | |||
fa97488bdc | |||
3086081ec0 | |||
dfab44c24a | |||
1900246054 | |||
e980f92c63 | |||
ce3800e3ef | |||
e3d668f450 | |||
75b4bc5896 | |||
c0ecf32d17 | |||
3155f07ccd | |||
02bead5097 | |||
e8b29c9fd1 | |||
610f27d684 | |||
59466f6678 | |||
3ddd8f860e | |||
8535ec0819 | |||
f294121ab7 | |||
0b8ebaf387 | |||
3b00cfccec | |||
005391be81 | |||
bc8fffe347 | |||
914bbfd710 | |||
be31bd2bbd | |||
95ff817019 | |||
ea91e7638c | |||
4564e9810c | |||
44a1a8f6be | |||
9cecb22170 | |||
7693502aaa | |||
c0fae547e9 | |||
0ba77a0d87 | |||
661dcbb6ca | |||
578440a18d | |||
93485df3d2 | |||
121fab61f1 | |||
915eec7943 | |||
8432a1c758 | |||
4a5e108527 | |||
6dd5171fdf | |||
c2b6cb5c3a | |||
1f93040af6 | |||
52f8ccd06c | |||
3b86feab8a | |||
aba51d99a1 | |||
8454e8417e | |||
422e8285b5 | |||
bb6ca6b100 | |||
1483c1f545 | |||
3d6a68de77 | |||
387d387080 | |||
94ed35e781 | |||
8995b291a9 | |||
1942438b22 | |||
40d653e659 | |||
ddcd28e67f | |||
65f587d5e9 | |||
2fefd8cbbf | |||
ba17bdd366 | |||
c998e52cc0 | |||
3224369ac9 | |||
67666dbe0f | |||
d7430eb921 | |||
26a6b426f1 | |||
6f35ce67f7 | |||
3406d58797 | |||
7925458fc0 | |||
b6854aaf9f | |||
656f595226 | |||
280aaf9d3b | |||
7abdb05f91 | |||
af4464ce58 | |||
6ca0978777 | |||
8506194d2c | |||
3bd7614ade | |||
bd1b421491 | |||
920207f72c | |||
12fdc04f1b | |||
d0c744ae6a | |||
dc07fa7085 | |||
1c893996c0 | |||
a9ffc2a078 | |||
712bbfe575 | |||
0145fe3486 | |||
2a579eb27c | |||
5b42afa324 | |||
97713849a1 | |||
abc9c38224 | |||
f237ff049a | |||
00264650b5 | |||
2956d33bab | |||
c3eca637fb | |||
0d54bbff02 | |||
e65a9cbc00 | |||
3ed82c0882 | |||
475feb7e24 | |||
30b80cba92 | |||
fe519bd2a0 | |||
f508f8d959 | |||
e295c3c7c3 | |||
313be996cd | |||
2ae480556d | |||
7173fd7490 | |||
d792536c23 | |||
80aabdd372 | |||
6892369cc6 | |||
5b80a56c36 | |||
bf15535a5b | |||
edb1c52dee | |||
6e16b8cdcf | |||
24215fb3eb | |||
d02699a189 | |||
2034d1b101 | |||
5fe19d639f | |||
1dea80d4ea | |||
28f0008075 | |||
ffe1b00baf | |||
d2c289193a | |||
f867583256 | |||
98dfb92e17 | |||
c8f22be675 | |||
f03a7d1128 | |||
6da4a66c20 | |||
5e4241fc87 | |||
bf468c8d0c | |||
0d77a3f8d2 | |||
1b6edafc96 | |||
8152ade1b6 | |||
6350f8b904 | |||
0c5398a19c | |||
4b656268d3 | |||
10729a1fc5 | |||
cdb3ef808f | |||
a5a2471f2c | |||
6282747f6d | |||
d5fd5cad38 | |||
fab7c6b6d0 | |||
42ade6aa49 | |||
55d9b69cd9 | |||
2a8d6f943f | |||
6b0155c4fb | |||
e22a5143bc | |||
a17eb54515 | |||
d5cf1050db | |||
b021686521 | |||
e56d1b718f |
@ -1,4 +0,0 @@
|
|||||||
github.com/astaxie/beego/*/*:S1012
|
|
||||||
github.com/astaxie/beego/*:S1012
|
|
||||||
github.com/astaxie/beego/*/*:S1007
|
|
||||||
github.com/astaxie/beego/*:S1007
|
|
26
.travis.yml
26
.travis.yml
@ -1,18 +1,26 @@
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- "1.9.x"
|
- "1.13.x"
|
||||||
- "1.10.x"
|
|
||||||
- "1.11.x"
|
|
||||||
services:
|
services:
|
||||||
- redis-server
|
- redis-server
|
||||||
- mysql
|
- mysql
|
||||||
- postgresql
|
- postgresql
|
||||||
- memcached
|
- memcached
|
||||||
env:
|
env:
|
||||||
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
|
global:
|
||||||
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
|
- GO_REPO_FULLNAME="github.com/astaxie/beego"
|
||||||
|
matrix:
|
||||||
|
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
|
||||||
|
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
|
||||||
before_install:
|
before_install:
|
||||||
|
# link the local repo with ${GOPATH}/src/<namespace>/<repo>
|
||||||
|
- GO_REPO_NAMESPACE=${GO_REPO_FULLNAME%/*}
|
||||||
|
# relies on GOPATH to contain only one directory...
|
||||||
|
- mkdir -p ${GOPATH}/src/${GO_REPO_NAMESPACE}
|
||||||
|
- ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH}/src/${GO_REPO_FULLNAME}
|
||||||
|
- cd ${GOPATH}/src/${GO_REPO_FULLNAME}
|
||||||
|
# get and build ssdb
|
||||||
- git clone git://github.com/ideawu/ssdb.git
|
- git clone git://github.com/ideawu/ssdb.git
|
||||||
- cd ssdb
|
- cd ssdb
|
||||||
- make
|
- make
|
||||||
@ -36,7 +44,9 @@ install:
|
|||||||
- go get github.com/Knetic/govaluate
|
- go get github.com/Knetic/govaluate
|
||||||
- go get github.com/casbin/casbin
|
- go get github.com/casbin/casbin
|
||||||
- go get github.com/elazarl/go-bindata-assetfs
|
- go get github.com/elazarl/go-bindata-assetfs
|
||||||
- go get -u honnef.co/go/tools/cmd/gosimple
|
- go get github.com/OwnLocal/goes
|
||||||
|
- go get github.com/shiena/ansicolor
|
||||||
|
- go get -u honnef.co/go/tools/cmd/staticcheck
|
||||||
- go get -u github.com/mdempsky/unconvert
|
- go get -u github.com/mdempsky/unconvert
|
||||||
- go get -u github.com/gordonklaus/ineffassign
|
- go get -u github.com/gordonklaus/ineffassign
|
||||||
- go get -u github.com/golang/lint/golint
|
- go get -u github.com/golang/lint/golint
|
||||||
@ -51,11 +61,11 @@ before_script:
|
|||||||
- mkdir -p res/var
|
- mkdir -p res/var
|
||||||
- ./ssdb/ssdb-server ./ssdb/ssdb.conf -d
|
- ./ssdb/ssdb-server ./ssdb/ssdb.conf -d
|
||||||
after_script:
|
after_script:
|
||||||
-killall -w ssdb-server
|
- killall -w ssdb-server
|
||||||
- rm -rf ./res/var/*
|
- rm -rf ./res/var/*
|
||||||
script:
|
script:
|
||||||
- go test -v ./...
|
- go test -v ./...
|
||||||
- gosimple -ignore "$(cat .gosimpleignore)" $(go list ./... | grep -v /vendor/)
|
- staticcheck -show-ignored -checks "-ST1017,-U1000,-ST1005,-S1034,-S1012,-SA4006,-SA6005,-SA1019,-SA1024"
|
||||||
- unconvert $(go list ./... | grep -v /vendor/)
|
- unconvert $(go list ./... | grep -v /vendor/)
|
||||||
- ineffassign .
|
- ineffassign .
|
||||||
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
|
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
|
||||||
|
@ -4,8 +4,6 @@
|
|||||||
beego is used for rapid development of RESTful APIs, web apps and backend services in Go.
|
beego is used for rapid development of RESTful APIs, web apps and backend services in Go.
|
||||||
It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding.
|
It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding.
|
||||||
|
|
||||||
Response time ranking: [web-frameworks](https://github.com/the-benchmarker/web-frameworks).
|
|
||||||
|
|
||||||
###### More info at [beego.me](http://beego.me).
|
###### More info at [beego.me](http://beego.me).
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
@ -56,6 +54,7 @@ Congratulations! You've just built your first **beego** app.
|
|||||||
|
|
||||||
* [http://beego.me/community](http://beego.me/community)
|
* [http://beego.me/community](http://beego.me/community)
|
||||||
* Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited from [here](https://github.com/beego/beedoc/issues/232)
|
* Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited from [here](https://github.com/beego/beedoc/issues/232)
|
||||||
|
* QQ Group Group ID:523992905
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
3
admin.go
3
admin.go
@ -20,11 +20,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/astaxie/beego/grace"
|
"github.com/astaxie/beego/grace"
|
||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
"github.com/astaxie/beego/toolbox"
|
"github.com/astaxie/beego/toolbox"
|
||||||
|
4
app.go
4
app.go
@ -176,7 +176,7 @@ func (app *App) Run(mws ...MiddleWare) {
|
|||||||
if BConfig.Listen.HTTPSPort != 0 {
|
if BConfig.Listen.HTTPSPort != 0 {
|
||||||
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
||||||
} else if BConfig.Listen.EnableHTTP {
|
} else if BConfig.Listen.EnableHTTP {
|
||||||
BeeLogger.Info("Start https server error, conflict with http. Please reset https port")
|
logs.Info("Start https server error, conflict with http. Please reset https port")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logs.Info("https server Running on https://%s", app.Server.Addr)
|
logs.Info("https server Running on https://%s", app.Server.Addr)
|
||||||
@ -192,7 +192,7 @@ func (app *App) Run(mws ...MiddleWare) {
|
|||||||
pool := x509.NewCertPool()
|
pool := x509.NewCertPool()
|
||||||
data, err := ioutil.ReadFile(BConfig.Listen.TrustCaFile)
|
data, err := ioutil.ReadFile(BConfig.Listen.TrustCaFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
BeeLogger.Info("MutualHTTPS should provide TrustCaFile")
|
logs.Info("MutualHTTPS should provide TrustCaFile")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pool.AppendCertsFromPEM(data)
|
pool.AppendCertsFromPEM(data)
|
||||||
|
2
beego.go
2
beego.go
@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// VERSION represent beego web framework version.
|
// VERSION represent beego web framework version.
|
||||||
VERSION = "1.11.0"
|
VERSION = "1.12.1"
|
||||||
|
|
||||||
// DEV is for develop
|
// DEV is for develop
|
||||||
DEV = "dev"
|
DEV = "dev"
|
||||||
|
25
cache/cache_test.go
vendored
25
cache/cache_test.go
vendored
@ -16,10 +16,33 @@ package cache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestCacheIncr(t *testing.T) {
|
||||||
|
bm, err := NewCache("memory", `{"interval":20}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
//timeoutDuration := 10 * time.Second
|
||||||
|
|
||||||
|
bm.Put("edwardhey", 0, time.Second*20)
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(10)
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
bm.Incr("edwardhey")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
if bm.Get("edwardhey").(int) != 10 {
|
||||||
|
t.Error("Incr err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCache(t *testing.T) {
|
func TestCache(t *testing.T) {
|
||||||
bm, err := NewCache("memory", `{"interval":20}`)
|
bm, err := NewCache("memory", `{"interval":20}`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -98,7 +121,7 @@ func TestCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileCache(t *testing.T) {
|
func TestFileCache(t *testing.T) {
|
||||||
bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}`)
|
bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("init err")
|
t.Error("init err")
|
||||||
}
|
}
|
||||||
|
17
cache/file.go
vendored
17
cache/file.go
vendored
@ -62,11 +62,14 @@ func NewFileCache() Cache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StartAndGC will start and begin gc for file cache.
|
// StartAndGC will start and begin gc for file cache.
|
||||||
// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}
|
// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}
|
||||||
func (fc *FileCache) StartAndGC(config string) error {
|
func (fc *FileCache) StartAndGC(config string) error {
|
||||||
|
|
||||||
var cfg map[string]string
|
cfg := make(map[string]string)
|
||||||
json.Unmarshal([]byte(config), &cfg)
|
err := json.Unmarshal([]byte(config), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if _, ok := cfg["CachePath"]; !ok {
|
if _, ok := cfg["CachePath"]; !ok {
|
||||||
cfg["CachePath"] = FileCachePath
|
cfg["CachePath"] = FileCachePath
|
||||||
}
|
}
|
||||||
@ -142,12 +145,12 @@ func (fc *FileCache) GetMulti(keys []string) []interface{} {
|
|||||||
|
|
||||||
// Put value into file cache.
|
// Put value into file cache.
|
||||||
// timeout means how long to keep this file, unit of ms.
|
// timeout means how long to keep this file, unit of ms.
|
||||||
// if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever.
|
// if timeout equals fc.EmbedExpiry(default is 0), cache this item forever.
|
||||||
func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error {
|
func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error {
|
||||||
gob.Register(val)
|
gob.Register(val)
|
||||||
|
|
||||||
item := FileCacheItem{Data: val}
|
item := FileCacheItem{Data: val}
|
||||||
if timeout == FileCacheEmbedExpiry {
|
if timeout == time.Duration(fc.EmbedExpiry) {
|
||||||
item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years
|
item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years
|
||||||
} else {
|
} else {
|
||||||
item.Expired = time.Now().Add(timeout)
|
item.Expired = time.Now().Add(timeout)
|
||||||
@ -179,7 +182,7 @@ func (fc *FileCache) Incr(key string) error {
|
|||||||
} else {
|
} else {
|
||||||
incr = data.(int) + 1
|
incr = data.(int) + 1
|
||||||
}
|
}
|
||||||
fc.Put(key, incr, FileCacheEmbedExpiry)
|
fc.Put(key, incr, time.Duration(fc.EmbedExpiry))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +195,7 @@ func (fc *FileCache) Decr(key string) error {
|
|||||||
} else {
|
} else {
|
||||||
decr = data.(int) - 1
|
decr = data.(int) - 1
|
||||||
}
|
}
|
||||||
fc.Put(key, decr, FileCacheEmbedExpiry)
|
fc.Put(key, decr, time.Duration(fc.EmbedExpiry))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
cache/memcache/memcache.go
vendored
2
cache/memcache/memcache.go
vendored
@ -146,7 +146,7 @@ func (rc *Cache) IsExist(key string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err := rc.conn.Get(key)
|
_, err := rc.conn.Get(key)
|
||||||
return !(err != nil)
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearAll clear all cached in memcache.
|
// ClearAll clear all cached in memcache.
|
||||||
|
42
cache/memory.go
vendored
42
cache/memory.go
vendored
@ -110,25 +110,25 @@ func (bc *MemoryCache) Delete(name string) error {
|
|||||||
// Incr increase cache counter in memory.
|
// Incr increase cache counter in memory.
|
||||||
// it supports int,int32,int64,uint,uint32,uint64.
|
// it supports int,int32,int64,uint,uint32,uint64.
|
||||||
func (bc *MemoryCache) Incr(key string) error {
|
func (bc *MemoryCache) Incr(key string) error {
|
||||||
bc.RLock()
|
bc.Lock()
|
||||||
defer bc.RUnlock()
|
defer bc.Unlock()
|
||||||
itm, ok := bc.items[key]
|
itm, ok := bc.items[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("key not exist")
|
return errors.New("key not exist")
|
||||||
}
|
}
|
||||||
switch itm.val.(type) {
|
switch val := itm.val.(type) {
|
||||||
case int:
|
case int:
|
||||||
itm.val = itm.val.(int) + 1
|
itm.val = val + 1
|
||||||
case int32:
|
case int32:
|
||||||
itm.val = itm.val.(int32) + 1
|
itm.val = val + 1
|
||||||
case int64:
|
case int64:
|
||||||
itm.val = itm.val.(int64) + 1
|
itm.val = val + 1
|
||||||
case uint:
|
case uint:
|
||||||
itm.val = itm.val.(uint) + 1
|
itm.val = val + 1
|
||||||
case uint32:
|
case uint32:
|
||||||
itm.val = itm.val.(uint32) + 1
|
itm.val = val + 1
|
||||||
case uint64:
|
case uint64:
|
||||||
itm.val = itm.val.(uint64) + 1
|
itm.val = val + 1
|
||||||
default:
|
default:
|
||||||
return errors.New("item val is not (u)int (u)int32 (u)int64")
|
return errors.New("item val is not (u)int (u)int32 (u)int64")
|
||||||
}
|
}
|
||||||
@ -137,34 +137,34 @@ func (bc *MemoryCache) Incr(key string) error {
|
|||||||
|
|
||||||
// Decr decrease counter in memory.
|
// Decr decrease counter in memory.
|
||||||
func (bc *MemoryCache) Decr(key string) error {
|
func (bc *MemoryCache) Decr(key string) error {
|
||||||
bc.RLock()
|
bc.Lock()
|
||||||
defer bc.RUnlock()
|
defer bc.Unlock()
|
||||||
itm, ok := bc.items[key]
|
itm, ok := bc.items[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("key not exist")
|
return errors.New("key not exist")
|
||||||
}
|
}
|
||||||
switch itm.val.(type) {
|
switch val := itm.val.(type) {
|
||||||
case int:
|
case int:
|
||||||
itm.val = itm.val.(int) - 1
|
itm.val = val - 1
|
||||||
case int64:
|
case int64:
|
||||||
itm.val = itm.val.(int64) - 1
|
itm.val = val - 1
|
||||||
case int32:
|
case int32:
|
||||||
itm.val = itm.val.(int32) - 1
|
itm.val = val - 1
|
||||||
case uint:
|
case uint:
|
||||||
if itm.val.(uint) > 0 {
|
if val > 0 {
|
||||||
itm.val = itm.val.(uint) - 1
|
itm.val = val - 1
|
||||||
} else {
|
} else {
|
||||||
return errors.New("item val is less than 0")
|
return errors.New("item val is less than 0")
|
||||||
}
|
}
|
||||||
case uint32:
|
case uint32:
|
||||||
if itm.val.(uint32) > 0 {
|
if val > 0 {
|
||||||
itm.val = itm.val.(uint32) - 1
|
itm.val = val - 1
|
||||||
} else {
|
} else {
|
||||||
return errors.New("item val is less than 0")
|
return errors.New("item val is less than 0")
|
||||||
}
|
}
|
||||||
case uint64:
|
case uint64:
|
||||||
if itm.val.(uint64) > 0 {
|
if val > 0 {
|
||||||
itm.val = itm.val.(uint64) - 1
|
itm.val = val - 1
|
||||||
} else {
|
} else {
|
||||||
return errors.New("item val is less than 0")
|
return errors.New("item val is less than 0")
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ func parseYML(buf []byte) (cnf map[string]interface{}, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := goyaml2.Read(bytes.NewBuffer(buf))
|
data, err := goyaml2.Read(bytes.NewReader(buf))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Goyaml2 ERR>", string(buf), err)
|
log.Println("Goyaml2 ERR>", string(buf), err)
|
||||||
return
|
return
|
||||||
|
@ -25,7 +25,7 @@ package context
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha1"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -123,7 +123,7 @@ func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) {
|
|||||||
timestamp := parts[1]
|
timestamp := parts[1]
|
||||||
sig := parts[2]
|
sig := parts[2]
|
||||||
|
|
||||||
h := hmac.New(sha1.New, []byte(Secret))
|
h := hmac.New(sha256.New, []byte(Secret))
|
||||||
fmt.Fprintf(h, "%s%s", vs, timestamp)
|
fmt.Fprintf(h, "%s%s", vs, timestamp)
|
||||||
|
|
||||||
if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
|
if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
|
||||||
@ -137,7 +137,7 @@ func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) {
|
|||||||
func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) {
|
func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) {
|
||||||
vs := base64.URLEncoding.EncodeToString([]byte(value))
|
vs := base64.URLEncoding.EncodeToString([]byte(value))
|
||||||
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
|
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||||
h := hmac.New(sha1.New, []byte(Secret))
|
h := hmac.New(sha256.New, []byte(Secret))
|
||||||
fmt.Fprintf(h, "%s%s", vs, timestamp)
|
fmt.Fprintf(h, "%s%s", vs, timestamp)
|
||||||
sig := fmt.Sprintf("%02x", h.Sum(nil))
|
sig := fmt.Sprintf("%02x", h.Sum(nil))
|
||||||
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
|
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
|
||||||
@ -169,11 +169,11 @@ func (ctx *Context) CheckXSRFCookie() bool {
|
|||||||
token = ctx.Request.Header.Get("X-Csrftoken")
|
token = ctx.Request.Header.Get("X-Csrftoken")
|
||||||
}
|
}
|
||||||
if token == "" {
|
if token == "" {
|
||||||
ctx.Abort(403, "'_xsrf' argument missing from POST")
|
ctx.Abort(422, "422")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if ctx._xsrfToken != token {
|
if ctx._xsrfToken != token {
|
||||||
ctx.Abort(403, "XSRF cookie does not match POST argument")
|
ctx.Abort(417, "417")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -201,6 +201,7 @@ type Response struct {
|
|||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
Started bool
|
Started bool
|
||||||
Status int
|
Status int
|
||||||
|
Elapsed time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Response) reset(rw http.ResponseWriter) {
|
func (r *Response) reset(rw http.ResponseWriter) {
|
||||||
@ -259,4 +260,4 @@ func (r *Response) Pusher() (pusher http.Pusher) {
|
|||||||
return pusher
|
return pusher
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/astaxie/beego/session"
|
"github.com/astaxie/beego/session"
|
||||||
)
|
)
|
||||||
@ -49,6 +50,7 @@ type BeegoInput struct {
|
|||||||
pnames []string
|
pnames []string
|
||||||
pvalues []string
|
pvalues []string
|
||||||
data map[interface{}]interface{} // store some values in this context when calling context in filter or controller.
|
data map[interface{}]interface{} // store some values in this context when calling context in filter or controller.
|
||||||
|
dataLock sync.RWMutex
|
||||||
RequestBody []byte
|
RequestBody []byte
|
||||||
RunMethod string
|
RunMethod string
|
||||||
RunController reflect.Type
|
RunController reflect.Type
|
||||||
@ -204,6 +206,7 @@ func (input *BeegoInput) AcceptsXML() bool {
|
|||||||
func (input *BeegoInput) AcceptsJSON() bool {
|
func (input *BeegoInput) AcceptsJSON() bool {
|
||||||
return acceptsJSONRegex.MatchString(input.Header("Accept"))
|
return acceptsJSONRegex.MatchString(input.Header("Accept"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptsYAML Checks if request accepts json response
|
// AcceptsYAML Checks if request accepts json response
|
||||||
func (input *BeegoInput) AcceptsYAML() bool {
|
func (input *BeegoInput) AcceptsYAML() bool {
|
||||||
return acceptsYAMLRegex.MatchString(input.Header("Accept"))
|
return acceptsYAMLRegex.MatchString(input.Header("Accept"))
|
||||||
@ -377,6 +380,8 @@ func (input *BeegoInput) CopyBody(MaxMemory int64) []byte {
|
|||||||
|
|
||||||
// Data return the implicit data in the input
|
// Data return the implicit data in the input
|
||||||
func (input *BeegoInput) Data() map[interface{}]interface{} {
|
func (input *BeegoInput) Data() map[interface{}]interface{} {
|
||||||
|
input.dataLock.Lock()
|
||||||
|
defer input.dataLock.Unlock()
|
||||||
if input.data == nil {
|
if input.data == nil {
|
||||||
input.data = make(map[interface{}]interface{})
|
input.data = make(map[interface{}]interface{})
|
||||||
}
|
}
|
||||||
@ -385,6 +390,8 @@ func (input *BeegoInput) Data() map[interface{}]interface{} {
|
|||||||
|
|
||||||
// GetData returns the stored data in this context.
|
// GetData returns the stored data in this context.
|
||||||
func (input *BeegoInput) GetData(key interface{}) interface{} {
|
func (input *BeegoInput) GetData(key interface{}) interface{} {
|
||||||
|
input.dataLock.Lock()
|
||||||
|
defer input.dataLock.Unlock()
|
||||||
if v, ok := input.data[key]; ok {
|
if v, ok := input.data[key]; ok {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
@ -394,6 +401,8 @@ func (input *BeegoInput) GetData(key interface{}) interface{} {
|
|||||||
// SetData stores data with given key in this context.
|
// SetData stores data with given key in this context.
|
||||||
// This data are only available in this context.
|
// This data are only available in this context.
|
||||||
func (input *BeegoInput) SetData(key, val interface{}) {
|
func (input *BeegoInput) SetData(key, val interface{}) {
|
||||||
|
input.dataLock.Lock()
|
||||||
|
defer input.dataLock.Unlock()
|
||||||
if input.data == nil {
|
if input.data == nil {
|
||||||
input.data = make(map[interface{}]interface{})
|
input.data = make(map[interface{}]interface{})
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BeegoOutput does work for sending response header.
|
// BeegoOutput does work for sending response header.
|
||||||
@ -203,7 +204,6 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool)
|
|||||||
return output.Body(content)
|
return output.Body(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// YAML writes yaml to response body.
|
// YAML writes yaml to response body.
|
||||||
func (output *BeegoOutput) YAML(data interface{}) error {
|
func (output *BeegoOutput) YAML(data interface{}) error {
|
||||||
output.Header("Content-Type", "application/x-yaml; charset=utf-8")
|
output.Header("Content-Type", "application/x-yaml; charset=utf-8")
|
||||||
@ -288,7 +288,20 @@ func (output *BeegoOutput) Download(file string, filename ...string) {
|
|||||||
} else {
|
} else {
|
||||||
fName = filepath.Base(file)
|
fName = filepath.Base(file)
|
||||||
}
|
}
|
||||||
output.Header("Content-Disposition", "attachment; filename="+url.PathEscape(fName))
|
//https://tools.ietf.org/html/rfc6266#section-4.3
|
||||||
|
fn := url.PathEscape(fName)
|
||||||
|
if fName == fn {
|
||||||
|
fn = "filename=" + fn
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
The parameters "filename" and "filename*" differ only in that
|
||||||
|
"filename*" uses the encoding defined in [RFC5987], allowing the use
|
||||||
|
of characters not present in the ISO-8859-1 character set
|
||||||
|
([ISO-8859-1]).
|
||||||
|
*/
|
||||||
|
fn = "filename=" + fName + "; filename*=utf-8''" + fn
|
||||||
|
}
|
||||||
|
output.Header("Content-Disposition", "attachment; "+fn)
|
||||||
output.Header("Content-Description", "File Transfer")
|
output.Header("Content-Description", "File Transfer")
|
||||||
output.Header("Content-Type", "application/octet-stream")
|
output.Header("Content-Type", "application/octet-stream")
|
||||||
output.Header("Content-Transfer-Encoding", "binary")
|
output.Header("Content-Transfer-Encoding", "binary")
|
||||||
|
@ -17,6 +17,7 @@ package beego
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
@ -34,7 +35,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrAbort custom error when user stop request handler manually.
|
// ErrAbort custom error when user stop request handler manually.
|
||||||
ErrAbort = errors.New("User stop run")
|
ErrAbort = errors.New("user stop run")
|
||||||
// GlobalControllerRouter store comments with controller. pkgpath+controller:comments
|
// GlobalControllerRouter store comments with controller. pkgpath+controller:comments
|
||||||
GlobalControllerRouter = make(map[string][]ControllerComments)
|
GlobalControllerRouter = make(map[string][]ControllerComments)
|
||||||
)
|
)
|
||||||
@ -93,7 +94,6 @@ type Controller struct {
|
|||||||
controllerName string
|
controllerName string
|
||||||
actionName string
|
actionName string
|
||||||
methodMapping map[string]func() //method:routertree
|
methodMapping map[string]func() //method:routertree
|
||||||
gotofunc string
|
|
||||||
AppController interface{}
|
AppController interface{}
|
||||||
|
|
||||||
// template data
|
// template data
|
||||||
@ -125,6 +125,7 @@ type ControllerInterface interface {
|
|||||||
Head()
|
Head()
|
||||||
Patch()
|
Patch()
|
||||||
Options()
|
Options()
|
||||||
|
Trace()
|
||||||
Finish()
|
Finish()
|
||||||
Render() error
|
Render() error
|
||||||
XSRFToken() string
|
XSRFToken() string
|
||||||
@ -156,37 +157,59 @@ func (c *Controller) Finish() {}
|
|||||||
|
|
||||||
// Get adds a request function to handle GET request.
|
// Get adds a request function to handle GET request.
|
||||||
func (c *Controller) Get() {
|
func (c *Controller) Get() {
|
||||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post adds a request function to handle POST request.
|
// Post adds a request function to handle POST request.
|
||||||
func (c *Controller) Post() {
|
func (c *Controller) Post() {
|
||||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete adds a request function to handle DELETE request.
|
// Delete adds a request function to handle DELETE request.
|
||||||
func (c *Controller) Delete() {
|
func (c *Controller) Delete() {
|
||||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put adds a request function to handle PUT request.
|
// Put adds a request function to handle PUT request.
|
||||||
func (c *Controller) Put() {
|
func (c *Controller) Put() {
|
||||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Head adds a request function to handle HEAD request.
|
// Head adds a request function to handle HEAD request.
|
||||||
func (c *Controller) Head() {
|
func (c *Controller) Head() {
|
||||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch adds a request function to handle PATCH request.
|
// Patch adds a request function to handle PATCH request.
|
||||||
func (c *Controller) Patch() {
|
func (c *Controller) Patch() {
|
||||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options adds a request function to handle OPTIONS request.
|
// Options adds a request function to handle OPTIONS request.
|
||||||
func (c *Controller) Options() {
|
func (c *Controller) Options() {
|
||||||
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
|
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace adds a request function to handle Trace request.
|
||||||
|
// this method SHOULD NOT be overridden.
|
||||||
|
// https://tools.ietf.org/html/rfc7231#section-4.3.8
|
||||||
|
// The TRACE method requests a remote, application-level loop-back of
|
||||||
|
// the request message. The final recipient of the request SHOULD
|
||||||
|
// reflect the message received, excluding some fields described below,
|
||||||
|
// back to the client as the message body of a 200 (OK) response with a
|
||||||
|
// Content-Type of "message/http" (Section 8.3.1 of [RFC7230]).
|
||||||
|
func (c *Controller) Trace() {
|
||||||
|
ts := func(h http.Header) (hs string) {
|
||||||
|
for k, v := range h {
|
||||||
|
hs += fmt.Sprintf("\r\n%s: %s", k, v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hs := fmt.Sprintf("\r\nTRACE %s %s%s\r\n", c.Ctx.Request.RequestURI, c.Ctx.Request.Proto, ts(c.Ctx.Request.Header))
|
||||||
|
c.Ctx.Output.Header("Content-Type", "message/http")
|
||||||
|
c.Ctx.Output.Header("Content-Length", fmt.Sprint(len(hs)))
|
||||||
|
c.Ctx.Output.Header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
c.Ctx.WriteString(hs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlerFunc call function with the name
|
// HandlerFunc call function with the name
|
||||||
@ -292,7 +315,7 @@ func (c *Controller) viewPath() string {
|
|||||||
|
|
||||||
// Redirect sends the redirection response to url with status code.
|
// Redirect sends the redirection response to url with status code.
|
||||||
func (c *Controller) Redirect(url string, code int) {
|
func (c *Controller) Redirect(url string, code int) {
|
||||||
logAccess(c.Ctx, nil, code)
|
LogAccess(c.Ctx, nil, code)
|
||||||
c.Ctx.Redirect(code, url)
|
c.Ctx.Redirect(code, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
error.go
2
error.go
@ -435,7 +435,7 @@ func exception(errCode string, ctx *context.Context) {
|
|||||||
|
|
||||||
func executeError(err *errorInfo, ctx *context.Context, code int) {
|
func executeError(err *errorInfo, ctx *context.Context, code int) {
|
||||||
//make sure to log the error in the access log
|
//make sure to log the error in the access log
|
||||||
logAccess(ctx, nil, code)
|
LogAccess(ctx, nil, code)
|
||||||
|
|
||||||
if err.errorType == errorTypeHandler {
|
if err.errorType == errorTypeHandler {
|
||||||
ctx.ResponseWriter.WriteHeader(code)
|
ctx.ResponseWriter.WriteHeader(code)
|
||||||
|
2
fs.go
2
fs.go
@ -42,13 +42,13 @@ func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.Wal
|
|||||||
}
|
}
|
||||||
|
|
||||||
dir, err := fs.Open(path)
|
dir, err := fs.Open(path)
|
||||||
defer dir.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err1 := walkFn(path, info, err); err1 != nil {
|
if err1 := walkFn(path, info, err); err1 != nil {
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer dir.Close()
|
||||||
dirs, err := dir.Readdir(-1)
|
dirs, err := dir.Readdir(-1)
|
||||||
err1 := walkFn(path, info, err)
|
err1 := walkFn(path, info, err)
|
||||||
// If err != nil, walk can't walk into this directory.
|
// If err != nil, walk can't walk into this directory.
|
||||||
|
29
go.mod
29
go.mod
@ -1,40 +1,41 @@
|
|||||||
module github.com/astaxie/beego
|
module github.com/astaxie/beego
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
|
||||||
github.com/Knetic/govaluate v3.0.0+incompatible // indirect
|
github.com/Knetic/govaluate v3.0.0+incompatible // indirect
|
||||||
|
github.com/OwnLocal/goes v1.0.0
|
||||||
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd
|
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd
|
||||||
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542
|
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542
|
||||||
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff
|
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737
|
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737
|
||||||
github.com/casbin/casbin v1.6.0
|
github.com/casbin/casbin v1.7.0
|
||||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58
|
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58
|
||||||
github.com/couchbase/go-couchbase v0.0.0-20181019154153-595f46701cbc
|
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb
|
||||||
github.com/couchbase/gomemcached v0.0.0-20180723192129-20e69a1ee160 // indirect
|
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c // indirect
|
||||||
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a // indirect
|
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a // indirect
|
||||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 // indirect
|
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect
|
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect
|
||||||
github.com/elazarl/go-bindata-assetfs v0.0.0-20180223110309-38087fe4dafb
|
github.com/elazarl/go-bindata-assetfs v1.0.0
|
||||||
github.com/go-redis/redis v6.14.2+incompatible
|
github.com/go-redis/redis v6.14.2+incompatible
|
||||||
github.com/go-sql-driver/mysql v1.4.0
|
github.com/go-sql-driver/mysql v1.4.1
|
||||||
github.com/gogo/protobuf v1.1.1
|
github.com/gogo/protobuf v1.1.1
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible
|
github.com/gomodule/redigo v2.0.0+incompatible
|
||||||
github.com/lib/pq v1.0.0
|
github.com/lib/pq v1.0.0
|
||||||
github.com/mattn/go-sqlite3 v1.10.0
|
github.com/mattn/go-sqlite3 v1.10.0
|
||||||
github.com/onsi/gomega v1.4.2 // indirect
|
|
||||||
github.com/pelletier/go-toml v1.2.0 // indirect
|
github.com/pelletier/go-toml v1.2.0 // indirect
|
||||||
github.com/pkg/errors v0.8.0 // indirect
|
github.com/pkg/errors v0.8.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect
|
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect
|
||||||
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373
|
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373
|
||||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d // indirect
|
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d // indirect
|
||||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec
|
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec
|
||||||
github.com/stretchr/testify v1.2.2 // indirect
|
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c // indirect
|
||||||
github.com/syndtr/goleveldb v0.0.0-20181105012736-f9080354173f // indirect
|
|
||||||
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b // indirect
|
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b // indirect
|
||||||
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
|
||||||
google.golang.org/appengine v1.1.0 // indirect
|
golang.org/x/tools v0.0.0-20200117065230-39095c1d176c
|
||||||
gopkg.in/yaml.v2 v2.2.1
|
gopkg.in/yaml.v2 v2.2.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 => github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85
|
||||||
|
|
||||||
|
replace gopkg.in/yaml.v2 v2.2.1 => github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
78
go.sum
78
go.sum
@ -1,7 +1,6 @@
|
|||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
|
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
|
||||||
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
|
github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM=
|
||||||
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd h1:jZtX5jh5IOMu0fpOTC3ayh6QGSPJ/KWOv1lgPvbRw1M=
|
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd h1:jZtX5jh5IOMu0fpOTC3ayh6QGSPJ/KWOv1lgPvbRw1M=
|
||||||
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
|
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
|
||||||
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542 h1:nYXb+3jF6Oq/j8R/y90XrKpreCxIalBWfeyeKymgOPk=
|
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542 h1:nYXb+3jF6Oq/j8R/y90XrKpreCxIalBWfeyeKymgOPk=
|
||||||
@ -10,54 +9,44 @@ github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff h1:/kO0p2RTGLB8R5gub7
|
|||||||
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff/go.mod h1:PhH1ZhyCzHKt4uAasyx+ljRCgoezetRNf59CUtwUkqY=
|
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff/go.mod h1:PhH1ZhyCzHKt4uAasyx+ljRCgoezetRNf59CUtwUkqY=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g=
|
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||||
github.com/casbin/casbin v1.6.0 h1:uIhuV5I0ilXGUm3y+xJ8nG7VOnYDeZZQiNsFOTF2QmI=
|
github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ=
|
||||||
github.com/casbin/casbin v1.6.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
||||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
|
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
|
||||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||||
github.com/couchbase/go-couchbase v0.0.0-20181019154153-595f46701cbc h1:Byzmalcea3rzOdgt4Ny3xrtXkd25zUMPFI5oeKksSbU=
|
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb h1:w3RapLhkA5+km9Z8vUkC6VCaskduJXvXwJg5neKnfDU=
|
||||||
github.com/couchbase/go-couchbase v0.0.0-20181019154153-595f46701cbc/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
|
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
|
||||||
github.com/couchbase/gomemcached v0.0.0-20180723192129-20e69a1ee160 h1:yaqs73s76owCkJbPZo8GKSosZoMjezdLDslJ8aaDk0w=
|
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c h1:K4FIibkr4//ziZKOKmt4RL0YImuTjLLBtwElf+F2lSQ=
|
||||||
github.com/couchbase/gomemcached v0.0.0-20180723192129-20e69a1ee160/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
|
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
|
||||||
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a h1:Y5XsLCEhtEI8qbD9RP3Qlv5FXdTDHxZM9UPUnMRgBp8=
|
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a h1:Y5XsLCEhtEI8qbD9RP3Qlv5FXdTDHxZM9UPUnMRgBp8=
|
||||||
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
|
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
|
||||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
|
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
|
||||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
|
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=
|
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=
|
||||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||||
github.com/elazarl/go-bindata-assetfs v0.0.0-20180223110309-38087fe4dafb h1:T6FhFH6fLQPEu7n7PauDhb4mhpxhlfaL7a7MZEpIgDc=
|
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
|
||||||
github.com/elazarl/go-bindata-assetfs v0.0.0-20180223110309-38087fe4dafb/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||||
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=
|
|
||||||
github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0=
|
github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0=
|
||||||
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
|
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d h1:xy93KVe+KrIIwWDEAfQBdIfsiHJkepbYsDr+VY3g9/o=
|
||||||
|
github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:B7ZbAFz7NOmvpUE5RGtu3u0WIizy5GdvbNpEf4RPnWs=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:uZvAcrsnNaCxlh1HorK5dUQHGmEKPh2H/Rl1kehswPo=
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
||||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
|
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
|
||||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
|
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
|
|
||||||
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
|
||||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
|
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
|
||||||
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
||||||
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373 h1:p6IxqQMjab30l4lb9mmkIkkcE1yv6o0SKbPhW5pxqHI=
|
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373 h1:p6IxqQMjab30l4lb9mmkIkkcE1yv6o0SKbPhW5pxqHI=
|
||||||
@ -66,29 +55,26 @@ github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d h1:NVwnfyR3rENtlz62
|
|||||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
|
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
|
||||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec h1:q6XVwXmKvCRHRqesF3cSv6lNqqHi0QWOvgDlSohg8UA=
|
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec h1:q6XVwXmKvCRHRqesF3cSv6lNqqHi0QWOvgDlSohg8UA=
|
||||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
|
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c h1:3eGShk3EQf5gJCYW+WzA0TEJQd37HLOmlYF7N0YJwv0=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||||
github.com/syndtr/goleveldb v0.0.0-20181105012736-f9080354173f h1:EEVjSRihF8NIbfyCcErpSpNHEKrY3s8EAwqiPENZZn8=
|
|
||||||
github.com/syndtr/goleveldb v0.0.0-20181105012736-f9080354173f/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
|
||||||
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b h1:0Ve0/CCjiAiyKddUMUn3RwIGlq2iTW4GuVzyoKBYO/8=
|
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b h1:0Ve0/CCjiAiyKddUMUn3RwIGlq2iTW4GuVzyoKBYO/8=
|
||||||
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
|
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
|
||||||
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb h1:Ah9YqXLj6fEgeKqcmBuLCbAsrF3ScD7dJ/bYM0C6tXI=
|
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
|
||||||
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
|
golang.org/x/tools v0.0.0-20200117065230-39095c1d176c h1:FodBYPZKH5tAN2O60HlglMwXGAeV/4k+NKbli79M/2c=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
golang.org/x/tools v0.0.0-20200117065230-39095c1d176c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
package grace
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type graceConn struct {
|
|
||||||
net.Conn
|
|
||||||
server *Server
|
|
||||||
m sync.Mutex
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *graceConn) Close() (err error) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
switch x := r.(type) {
|
|
||||||
case string:
|
|
||||||
err = errors.New(x)
|
|
||||||
case error:
|
|
||||||
err = x
|
|
||||||
default:
|
|
||||||
err = errors.New("Unknown panic")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
c.m.Lock()
|
|
||||||
if c.closed {
|
|
||||||
c.m.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.server.wg.Done()
|
|
||||||
c.closed = true
|
|
||||||
c.m.Unlock()
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
@ -78,7 +78,7 @@ var (
|
|||||||
DefaultReadTimeOut time.Duration
|
DefaultReadTimeOut time.Duration
|
||||||
// DefaultWriteTimeOut is the HTTP Write timeout
|
// DefaultWriteTimeOut is the HTTP Write timeout
|
||||||
DefaultWriteTimeOut time.Duration
|
DefaultWriteTimeOut time.Duration
|
||||||
// DefaultMaxHeaderBytes is the Max HTTP Herder size, default is 0, no limit
|
// DefaultMaxHeaderBytes is the Max HTTP Header size, default is 0, no limit
|
||||||
DefaultMaxHeaderBytes int
|
DefaultMaxHeaderBytes int
|
||||||
// DefaultTimeout is the shutdown server's timeout. default is 60s
|
// DefaultTimeout is the shutdown server's timeout. default is 60s
|
||||||
DefaultTimeout = 60 * time.Second
|
DefaultTimeout = 60 * time.Second
|
||||||
@ -122,7 +122,6 @@ func NewServer(addr string, handler http.Handler) (srv *Server) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
srv = &Server{
|
srv = &Server{
|
||||||
wg: sync.WaitGroup{},
|
|
||||||
sigChan: make(chan os.Signal),
|
sigChan: make(chan os.Signal),
|
||||||
isChild: isChild,
|
isChild: isChild,
|
||||||
SignalHooks: map[int]map[os.Signal][]func(){
|
SignalHooks: map[int]map[os.Signal][]func(){
|
||||||
@ -137,20 +136,21 @@ func NewServer(addr string, handler http.Handler) (srv *Server) {
|
|||||||
syscall.SIGTERM: {},
|
syscall.SIGTERM: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
state: StateInit,
|
state: StateInit,
|
||||||
Network: "tcp",
|
Network: "tcp",
|
||||||
|
terminalChan: make(chan error), //no cache channel
|
||||||
|
}
|
||||||
|
srv.Server = &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
ReadTimeout: DefaultReadTimeOut,
|
||||||
|
WriteTimeout: DefaultWriteTimeOut,
|
||||||
|
MaxHeaderBytes: DefaultMaxHeaderBytes,
|
||||||
|
Handler: handler,
|
||||||
}
|
}
|
||||||
srv.Server = &http.Server{}
|
|
||||||
srv.Server.Addr = addr
|
|
||||||
srv.Server.ReadTimeout = DefaultReadTimeOut
|
|
||||||
srv.Server.WriteTimeout = DefaultWriteTimeOut
|
|
||||||
srv.Server.MaxHeaderBytes = DefaultMaxHeaderBytes
|
|
||||||
srv.Server.Handler = handler
|
|
||||||
|
|
||||||
runningServersOrder = append(runningServersOrder, addr)
|
runningServersOrder = append(runningServersOrder, addr)
|
||||||
runningServers[addr] = srv
|
runningServers[addr] = srv
|
||||||
|
return srv
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenAndServe refer http.ListenAndServe
|
// ListenAndServe refer http.ListenAndServe
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
package grace
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type graceListener struct {
|
|
||||||
net.Listener
|
|
||||||
stop chan error
|
|
||||||
stopped bool
|
|
||||||
server *Server
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGraceListener(l net.Listener, srv *Server) (el *graceListener) {
|
|
||||||
el = &graceListener{
|
|
||||||
Listener: l,
|
|
||||||
stop: make(chan error),
|
|
||||||
server: srv,
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
<-el.stop
|
|
||||||
el.stopped = true
|
|
||||||
el.stop <- el.Listener.Close()
|
|
||||||
}()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gl *graceListener) Accept() (c net.Conn, err error) {
|
|
||||||
tc, err := gl.Listener.(*net.TCPListener).AcceptTCP()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tc.SetKeepAlive(true)
|
|
||||||
tc.SetKeepAlivePeriod(3 * time.Minute)
|
|
||||||
|
|
||||||
c = &graceConn{
|
|
||||||
Conn: tc,
|
|
||||||
server: gl.server,
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.server.wg.Add(1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gl *graceListener) Close() error {
|
|
||||||
if gl.stopped {
|
|
||||||
return syscall.EINVAL
|
|
||||||
}
|
|
||||||
gl.stop <- nil
|
|
||||||
return <-gl.stop
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gl *graceListener) File() *os.File {
|
|
||||||
// returns a dup(2) - FD_CLOEXEC flag *not* set
|
|
||||||
tl := gl.Listener.(*net.TCPListener)
|
|
||||||
fl, _ := tl.File()
|
|
||||||
return fl
|
|
||||||
}
|
|
110
grace/server.go
110
grace/server.go
@ -1,6 +1,7 @@
|
|||||||
package grace
|
package grace
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -12,7 +13,6 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -20,14 +20,13 @@ import (
|
|||||||
// Server embedded http.Server
|
// Server embedded http.Server
|
||||||
type Server struct {
|
type Server struct {
|
||||||
*http.Server
|
*http.Server
|
||||||
GraceListener net.Listener
|
ln net.Listener
|
||||||
SignalHooks map[int]map[os.Signal][]func()
|
SignalHooks map[int]map[os.Signal][]func()
|
||||||
tlsInnerListener *graceListener
|
sigChan chan os.Signal
|
||||||
wg sync.WaitGroup
|
isChild bool
|
||||||
sigChan chan os.Signal
|
state uint8
|
||||||
isChild bool
|
Network string
|
||||||
state uint8
|
terminalChan chan error
|
||||||
Network string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve accepts incoming connections on the Listener l,
|
// Serve accepts incoming connections on the Listener l,
|
||||||
@ -35,11 +34,19 @@ type Server struct {
|
|||||||
// The service goroutines read requests and then call srv.Handler to reply to them.
|
// The service goroutines read requests and then call srv.Handler to reply to them.
|
||||||
func (srv *Server) Serve() (err error) {
|
func (srv *Server) Serve() (err error) {
|
||||||
srv.state = StateRunning
|
srv.state = StateRunning
|
||||||
err = srv.Server.Serve(srv.GraceListener)
|
defer func() { srv.state = StateTerminate }()
|
||||||
log.Println(syscall.Getpid(), "Waiting for connections to finish...")
|
|
||||||
srv.wg.Wait()
|
// When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS
|
||||||
srv.state = StateTerminate
|
// immediately return ErrServerClosed. Make sure the program doesn't exit
|
||||||
return
|
// and waits instead for Shutdown to return.
|
||||||
|
if err = srv.Server.Serve(srv.ln); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Println(syscall.Getpid(), "Server.Serve() error:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(syscall.Getpid(), srv.ln.Addr(), "Listener closed.")
|
||||||
|
// wait for Shutdown to return
|
||||||
|
return <-srv.terminalChan
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenAndServe listens on the TCP network address srv.Addr and then calls Serve
|
// ListenAndServe listens on the TCP network address srv.Addr and then calls Serve
|
||||||
@ -53,14 +60,12 @@ func (srv *Server) ListenAndServe() (err error) {
|
|||||||
|
|
||||||
go srv.handleSignals()
|
go srv.handleSignals()
|
||||||
|
|
||||||
l, err := srv.getListener(addr)
|
srv.ln, err = srv.getListener(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.GraceListener = newGraceListener(l, srv)
|
|
||||||
|
|
||||||
if srv.isChild {
|
if srv.isChild {
|
||||||
process, err := os.FindProcess(os.Getppid())
|
process, err := os.FindProcess(os.Getppid())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -107,14 +112,12 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
|
|||||||
|
|
||||||
go srv.handleSignals()
|
go srv.handleSignals()
|
||||||
|
|
||||||
l, err := srv.getListener(addr)
|
ln, err := srv.getListener(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
srv.ln = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig)
|
||||||
srv.tlsInnerListener = newGraceListener(l, srv)
|
|
||||||
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
|
|
||||||
|
|
||||||
if srv.isChild {
|
if srv.isChild {
|
||||||
process, err := os.FindProcess(os.Getppid())
|
process, err := os.FindProcess(os.Getppid())
|
||||||
@ -127,6 +130,7 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println(os.Getpid(), srv.Addr)
|
log.Println(os.Getpid(), srv.Addr)
|
||||||
return srv.Serve()
|
return srv.Serve()
|
||||||
}
|
}
|
||||||
@ -163,14 +167,12 @@ func (srv *Server) ListenAndServeMutualTLS(certFile, keyFile, trustFile string)
|
|||||||
log.Println("Mutual HTTPS")
|
log.Println("Mutual HTTPS")
|
||||||
go srv.handleSignals()
|
go srv.handleSignals()
|
||||||
|
|
||||||
l, err := srv.getListener(addr)
|
ln, err := srv.getListener(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
srv.ln = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig)
|
||||||
srv.tlsInnerListener = newGraceListener(l, srv)
|
|
||||||
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
|
|
||||||
|
|
||||||
if srv.isChild {
|
if srv.isChild {
|
||||||
process, err := os.FindProcess(os.Getppid())
|
process, err := os.FindProcess(os.Getppid())
|
||||||
@ -183,6 +185,7 @@ func (srv *Server) ListenAndServeMutualTLS(certFile, keyFile, trustFile string)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println(os.Getpid(), srv.Addr)
|
log.Println(os.Getpid(), srv.Addr)
|
||||||
return srv.Serve()
|
return srv.Serve()
|
||||||
}
|
}
|
||||||
@ -213,6 +216,20 @@ func (srv *Server) getListener(laddr string) (l net.Listener, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type tcpKeepAliveListener struct {
|
||||||
|
*net.TCPListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||||
|
tc, err := ln.AcceptTCP()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tc.SetKeepAlive(true)
|
||||||
|
tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||||
|
return tc, nil
|
||||||
|
}
|
||||||
|
|
||||||
// handleSignals listens for os Signals and calls any hooked in function that the
|
// handleSignals listens for os Signals and calls any hooked in function that the
|
||||||
// user had registered with the signal.
|
// user had registered with the signal.
|
||||||
func (srv *Server) handleSignals() {
|
func (srv *Server) handleSignals() {
|
||||||
@ -265,37 +282,14 @@ func (srv *Server) shutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
srv.state = StateShuttingDown
|
srv.state = StateShuttingDown
|
||||||
|
log.Println(syscall.Getpid(), "Waiting for connections to finish...")
|
||||||
|
ctx := context.Background()
|
||||||
if DefaultTimeout >= 0 {
|
if DefaultTimeout >= 0 {
|
||||||
go srv.serverTimeout(DefaultTimeout)
|
var cancel context.CancelFunc
|
||||||
}
|
ctx, cancel = context.WithTimeout(context.Background(), DefaultTimeout)
|
||||||
err := srv.GraceListener.Close()
|
defer cancel()
|
||||||
if err != nil {
|
|
||||||
log.Println(syscall.Getpid(), "Listener.Close() error:", err)
|
|
||||||
} else {
|
|
||||||
log.Println(syscall.Getpid(), srv.GraceListener.Addr(), "Listener closed.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverTimeout forces the server to shutdown in a given timeout - whether it
|
|
||||||
// finished outstanding requests or not. if Read/WriteTimeout are not set or the
|
|
||||||
// max header size is very big a connection could hang
|
|
||||||
func (srv *Server) serverTimeout(d time.Duration) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
log.Println("WaitGroup at 0", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if srv.state != StateShuttingDown {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
time.Sleep(d)
|
|
||||||
log.Println("[STOP - Hammer Time] Forcefully shutting down parent")
|
|
||||||
for {
|
|
||||||
if srv.state == StateTerminate {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
srv.wg.Done()
|
|
||||||
}
|
}
|
||||||
|
srv.terminalChan <- srv.Server.Shutdown(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) fork() (err error) {
|
func (srv *Server) fork() (err error) {
|
||||||
@ -309,12 +303,8 @@ func (srv *Server) fork() (err error) {
|
|||||||
var files = make([]*os.File, len(runningServers))
|
var files = make([]*os.File, len(runningServers))
|
||||||
var orderArgs = make([]string, len(runningServers))
|
var orderArgs = make([]string, len(runningServers))
|
||||||
for _, srvPtr := range runningServers {
|
for _, srvPtr := range runningServers {
|
||||||
switch srvPtr.GraceListener.(type) {
|
f, _ := srvPtr.ln.(*net.TCPListener).File()
|
||||||
case *graceListener:
|
files[socketPtrOffsetMap[srvPtr.Server.Addr]] = f
|
||||||
files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.GraceListener.(*graceListener).File()
|
|
||||||
default:
|
|
||||||
files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.tlsInnerListener.File()
|
|
||||||
}
|
|
||||||
orderArgs[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.Server.Addr
|
orderArgs[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.Server.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
hooks.go
2
hooks.go
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/astaxie/beego/session"
|
"github.com/astaxie/beego/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
//
|
// register MIME type with content type
|
||||||
func registerMime() error {
|
func registerMime() error {
|
||||||
for k, v := range mimemaps {
|
for k, v := range mimemaps {
|
||||||
mime.AddExtensionType(k, v)
|
mime.AddExtensionType(k, v)
|
||||||
|
@ -47,9 +47,11 @@ import (
|
|||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -558,12 +560,6 @@ func (b *BeegoHTTPRequest) Bytes() ([]byte, error) {
|
|||||||
// ToFile saves the body data in response to one file.
|
// ToFile saves the body data in response to one file.
|
||||||
// it calls Response inner.
|
// it calls Response inner.
|
||||||
func (b *BeegoHTTPRequest) ToFile(filename string) error {
|
func (b *BeegoHTTPRequest) ToFile(filename string) error {
|
||||||
f, err := os.Create(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
resp, err := b.getResponse()
|
resp, err := b.getResponse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -572,10 +568,35 @@ func (b *BeegoHTTPRequest) ToFile(filename string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
err = pathExistAndMkdir(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
_, err = io.Copy(f, resp.Body)
|
_, err = io.Copy(f, resp.Body)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check that the file directory exists, there is no automatically created
|
||||||
|
func pathExistAndMkdir(filename string) (err error) {
|
||||||
|
filename = path.Dir(filename)
|
||||||
|
_, err = os.Stat(filename)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = os.MkdirAll(filename, os.ModePerm)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// ToJSON returns the map that marshals from the body bytes as json in response .
|
// ToJSON returns the map that marshals from the body bytes as json in response .
|
||||||
// it calls Response inner.
|
// it calls Response inner.
|
||||||
func (b *BeegoHTTPRequest) ToJSON(v interface{}) error {
|
func (b *BeegoHTTPRequest) ToJSON(v interface{}) error {
|
||||||
|
@ -206,10 +206,16 @@ func TestToJson(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Log(ip.Origin)
|
t.Log(ip.Origin)
|
||||||
|
ips := strings.Split(ip.Origin, ",")
|
||||||
if n := strings.Count(ip.Origin, "."); n != 3 {
|
if len(ips) == 0 {
|
||||||
t.Fatal("response is not valid ip")
|
t.Fatal("response is not valid ip")
|
||||||
}
|
}
|
||||||
|
for i := range ips {
|
||||||
|
if net.ParseIP(strings.TrimSpace(ips[i])).To4() == nil {
|
||||||
|
t.Fatal("response is not valid ip")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToFile(t *testing.T) {
|
func TestToFile(t *testing.T) {
|
||||||
@ -226,6 +232,20 @@ func TestToFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToFileDir(t *testing.T) {
|
||||||
|
f := "./files/beego_testfile"
|
||||||
|
req := Get("http://httpbin.org/ip")
|
||||||
|
err := req.ToFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll("./files")
|
||||||
|
b, err := ioutil.ReadFile(f)
|
||||||
|
if n := strings.Index(string(b), "origin"); n == -1 {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHeader(t *testing.T) {
|
func TestHeader(t *testing.T) {
|
||||||
req := Get("http://httpbin.org/headers")
|
req := Get("http://httpbin.org/headers")
|
||||||
req.Header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36")
|
req.Header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36")
|
||||||
|
16
log.go
16
log.go
@ -21,6 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Log levels to control the logging output.
|
// Log levels to control the logging output.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
const (
|
const (
|
||||||
LevelEmergency = iota
|
LevelEmergency = iota
|
||||||
LevelAlert
|
LevelAlert
|
||||||
@ -33,75 +34,90 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// BeeLogger references the used application logger.
|
// BeeLogger references the used application logger.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
var BeeLogger = logs.GetBeeLogger()
|
var BeeLogger = logs.GetBeeLogger()
|
||||||
|
|
||||||
// SetLevel sets the global log level used by the simple logger.
|
// SetLevel sets the global log level used by the simple logger.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func SetLevel(l int) {
|
func SetLevel(l int) {
|
||||||
logs.SetLevel(l)
|
logs.SetLevel(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLogFuncCall set the CallDepth, default is 3
|
// SetLogFuncCall set the CallDepth, default is 3
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func SetLogFuncCall(b bool) {
|
func SetLogFuncCall(b bool) {
|
||||||
logs.SetLogFuncCall(b)
|
logs.SetLogFuncCall(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLogger sets a new logger.
|
// SetLogger sets a new logger.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func SetLogger(adaptername string, config string) error {
|
func SetLogger(adaptername string, config string) error {
|
||||||
return logs.SetLogger(adaptername, config)
|
return logs.SetLogger(adaptername, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emergency logs a message at emergency level.
|
// Emergency logs a message at emergency level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func Emergency(v ...interface{}) {
|
func Emergency(v ...interface{}) {
|
||||||
logs.Emergency(generateFmtStr(len(v)), v...)
|
logs.Emergency(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alert logs a message at alert level.
|
// Alert logs a message at alert level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func Alert(v ...interface{}) {
|
func Alert(v ...interface{}) {
|
||||||
logs.Alert(generateFmtStr(len(v)), v...)
|
logs.Alert(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Critical logs a message at critical level.
|
// Critical logs a message at critical level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func Critical(v ...interface{}) {
|
func Critical(v ...interface{}) {
|
||||||
logs.Critical(generateFmtStr(len(v)), v...)
|
logs.Critical(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error logs a message at error level.
|
// Error logs a message at error level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func Error(v ...interface{}) {
|
func Error(v ...interface{}) {
|
||||||
logs.Error(generateFmtStr(len(v)), v...)
|
logs.Error(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning logs a message at warning level.
|
// Warning logs a message at warning level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func Warning(v ...interface{}) {
|
func Warning(v ...interface{}) {
|
||||||
logs.Warning(generateFmtStr(len(v)), v...)
|
logs.Warning(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warn compatibility alias for Warning()
|
// Warn compatibility alias for Warning()
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func Warn(v ...interface{}) {
|
func Warn(v ...interface{}) {
|
||||||
logs.Warn(generateFmtStr(len(v)), v...)
|
logs.Warn(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notice logs a message at notice level.
|
// Notice logs a message at notice level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func Notice(v ...interface{}) {
|
func Notice(v ...interface{}) {
|
||||||
logs.Notice(generateFmtStr(len(v)), v...)
|
logs.Notice(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Informational logs a message at info level.
|
// Informational logs a message at info level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func Informational(v ...interface{}) {
|
func Informational(v ...interface{}) {
|
||||||
logs.Informational(generateFmtStr(len(v)), v...)
|
logs.Informational(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info compatibility alias for Warning()
|
// Info compatibility alias for Warning()
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func Info(v ...interface{}) {
|
func Info(v ...interface{}) {
|
||||||
logs.Info(generateFmtStr(len(v)), v...)
|
logs.Info(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug logs a message at debug level.
|
// Debug logs a message at debug level.
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func Debug(v ...interface{}) {
|
func Debug(v ...interface{}) {
|
||||||
logs.Debug(generateFmtStr(len(v)), v...)
|
logs.Debug(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trace logs a message at trace level.
|
// Trace logs a message at trace level.
|
||||||
// compatibility alias for Warning()
|
// compatibility alias for Warning()
|
||||||
|
// Deprecated: use github.com/astaxie/beego/logs instead.
|
||||||
func Trace(v ...interface{}) {
|
func Trace(v ...interface{}) {
|
||||||
logs.Trace(generateFmtStr(len(v)), v...)
|
logs.Trace(generateFmtStr(len(v)), v...)
|
||||||
}
|
}
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package logs
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
type ansiColorWriter struct {
|
|
||||||
w io.Writer
|
|
||||||
mode outputMode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cw *ansiColorWriter) Write(p []byte) (int, error) {
|
|
||||||
return cw.w.Write(p)
|
|
||||||
}
|
|
@ -1,428 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package logs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
csiState int
|
|
||||||
parseResult int
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
outsideCsiCode csiState = iota
|
|
||||||
firstCsiCode
|
|
||||||
secondCsiCode
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
noConsole parseResult = iota
|
|
||||||
changedColor
|
|
||||||
unknown
|
|
||||||
)
|
|
||||||
|
|
||||||
type ansiColorWriter struct {
|
|
||||||
w io.Writer
|
|
||||||
mode outputMode
|
|
||||||
state csiState
|
|
||||||
paramStartBuf bytes.Buffer
|
|
||||||
paramBuf bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
firstCsiChar byte = '\x1b'
|
|
||||||
secondeCsiChar byte = '['
|
|
||||||
separatorChar byte = ';'
|
|
||||||
sgrCode byte = 'm'
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
foregroundBlue = uint16(0x0001)
|
|
||||||
foregroundGreen = uint16(0x0002)
|
|
||||||
foregroundRed = uint16(0x0004)
|
|
||||||
foregroundIntensity = uint16(0x0008)
|
|
||||||
backgroundBlue = uint16(0x0010)
|
|
||||||
backgroundGreen = uint16(0x0020)
|
|
||||||
backgroundRed = uint16(0x0040)
|
|
||||||
backgroundIntensity = uint16(0x0080)
|
|
||||||
underscore = uint16(0x8000)
|
|
||||||
|
|
||||||
foregroundMask = foregroundBlue | foregroundGreen | foregroundRed | foregroundIntensity
|
|
||||||
backgroundMask = backgroundBlue | backgroundGreen | backgroundRed | backgroundIntensity
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ansiReset = "0"
|
|
||||||
ansiIntensityOn = "1"
|
|
||||||
ansiIntensityOff = "21"
|
|
||||||
ansiUnderlineOn = "4"
|
|
||||||
ansiUnderlineOff = "24"
|
|
||||||
ansiBlinkOn = "5"
|
|
||||||
ansiBlinkOff = "25"
|
|
||||||
|
|
||||||
ansiForegroundBlack = "30"
|
|
||||||
ansiForegroundRed = "31"
|
|
||||||
ansiForegroundGreen = "32"
|
|
||||||
ansiForegroundYellow = "33"
|
|
||||||
ansiForegroundBlue = "34"
|
|
||||||
ansiForegroundMagenta = "35"
|
|
||||||
ansiForegroundCyan = "36"
|
|
||||||
ansiForegroundWhite = "37"
|
|
||||||
ansiForegroundDefault = "39"
|
|
||||||
|
|
||||||
ansiBackgroundBlack = "40"
|
|
||||||
ansiBackgroundRed = "41"
|
|
||||||
ansiBackgroundGreen = "42"
|
|
||||||
ansiBackgroundYellow = "43"
|
|
||||||
ansiBackgroundBlue = "44"
|
|
||||||
ansiBackgroundMagenta = "45"
|
|
||||||
ansiBackgroundCyan = "46"
|
|
||||||
ansiBackgroundWhite = "47"
|
|
||||||
ansiBackgroundDefault = "49"
|
|
||||||
|
|
||||||
ansiLightForegroundGray = "90"
|
|
||||||
ansiLightForegroundRed = "91"
|
|
||||||
ansiLightForegroundGreen = "92"
|
|
||||||
ansiLightForegroundYellow = "93"
|
|
||||||
ansiLightForegroundBlue = "94"
|
|
||||||
ansiLightForegroundMagenta = "95"
|
|
||||||
ansiLightForegroundCyan = "96"
|
|
||||||
ansiLightForegroundWhite = "97"
|
|
||||||
|
|
||||||
ansiLightBackgroundGray = "100"
|
|
||||||
ansiLightBackgroundRed = "101"
|
|
||||||
ansiLightBackgroundGreen = "102"
|
|
||||||
ansiLightBackgroundYellow = "103"
|
|
||||||
ansiLightBackgroundBlue = "104"
|
|
||||||
ansiLightBackgroundMagenta = "105"
|
|
||||||
ansiLightBackgroundCyan = "106"
|
|
||||||
ansiLightBackgroundWhite = "107"
|
|
||||||
)
|
|
||||||
|
|
||||||
type drawType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
foreground drawType = iota
|
|
||||||
background
|
|
||||||
)
|
|
||||||
|
|
||||||
type winColor struct {
|
|
||||||
code uint16
|
|
||||||
drawType drawType
|
|
||||||
}
|
|
||||||
|
|
||||||
var colorMap = map[string]winColor{
|
|
||||||
ansiForegroundBlack: {0, foreground},
|
|
||||||
ansiForegroundRed: {foregroundRed, foreground},
|
|
||||||
ansiForegroundGreen: {foregroundGreen, foreground},
|
|
||||||
ansiForegroundYellow: {foregroundRed | foregroundGreen, foreground},
|
|
||||||
ansiForegroundBlue: {foregroundBlue, foreground},
|
|
||||||
ansiForegroundMagenta: {foregroundRed | foregroundBlue, foreground},
|
|
||||||
ansiForegroundCyan: {foregroundGreen | foregroundBlue, foreground},
|
|
||||||
ansiForegroundWhite: {foregroundRed | foregroundGreen | foregroundBlue, foreground},
|
|
||||||
ansiForegroundDefault: {foregroundRed | foregroundGreen | foregroundBlue, foreground},
|
|
||||||
|
|
||||||
ansiBackgroundBlack: {0, background},
|
|
||||||
ansiBackgroundRed: {backgroundRed, background},
|
|
||||||
ansiBackgroundGreen: {backgroundGreen, background},
|
|
||||||
ansiBackgroundYellow: {backgroundRed | backgroundGreen, background},
|
|
||||||
ansiBackgroundBlue: {backgroundBlue, background},
|
|
||||||
ansiBackgroundMagenta: {backgroundRed | backgroundBlue, background},
|
|
||||||
ansiBackgroundCyan: {backgroundGreen | backgroundBlue, background},
|
|
||||||
ansiBackgroundWhite: {backgroundRed | backgroundGreen | backgroundBlue, background},
|
|
||||||
ansiBackgroundDefault: {0, background},
|
|
||||||
|
|
||||||
ansiLightForegroundGray: {foregroundIntensity, foreground},
|
|
||||||
ansiLightForegroundRed: {foregroundIntensity | foregroundRed, foreground},
|
|
||||||
ansiLightForegroundGreen: {foregroundIntensity | foregroundGreen, foreground},
|
|
||||||
ansiLightForegroundYellow: {foregroundIntensity | foregroundRed | foregroundGreen, foreground},
|
|
||||||
ansiLightForegroundBlue: {foregroundIntensity | foregroundBlue, foreground},
|
|
||||||
ansiLightForegroundMagenta: {foregroundIntensity | foregroundRed | foregroundBlue, foreground},
|
|
||||||
ansiLightForegroundCyan: {foregroundIntensity | foregroundGreen | foregroundBlue, foreground},
|
|
||||||
ansiLightForegroundWhite: {foregroundIntensity | foregroundRed | foregroundGreen | foregroundBlue, foreground},
|
|
||||||
|
|
||||||
ansiLightBackgroundGray: {backgroundIntensity, background},
|
|
||||||
ansiLightBackgroundRed: {backgroundIntensity | backgroundRed, background},
|
|
||||||
ansiLightBackgroundGreen: {backgroundIntensity | backgroundGreen, background},
|
|
||||||
ansiLightBackgroundYellow: {backgroundIntensity | backgroundRed | backgroundGreen, background},
|
|
||||||
ansiLightBackgroundBlue: {backgroundIntensity | backgroundBlue, background},
|
|
||||||
ansiLightBackgroundMagenta: {backgroundIntensity | backgroundRed | backgroundBlue, background},
|
|
||||||
ansiLightBackgroundCyan: {backgroundIntensity | backgroundGreen | backgroundBlue, background},
|
|
||||||
ansiLightBackgroundWhite: {backgroundIntensity | backgroundRed | backgroundGreen | backgroundBlue, background},
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
||||||
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
|
|
||||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
|
||||||
defaultAttr *textAttributes
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout))
|
|
||||||
if screenInfo != nil {
|
|
||||||
colorMap[ansiForegroundDefault] = winColor{
|
|
||||||
screenInfo.WAttributes & (foregroundRed | foregroundGreen | foregroundBlue),
|
|
||||||
foreground,
|
|
||||||
}
|
|
||||||
colorMap[ansiBackgroundDefault] = winColor{
|
|
||||||
screenInfo.WAttributes & (backgroundRed | backgroundGreen | backgroundBlue),
|
|
||||||
background,
|
|
||||||
}
|
|
||||||
defaultAttr = convertTextAttr(screenInfo.WAttributes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type coord struct {
|
|
||||||
X, Y int16
|
|
||||||
}
|
|
||||||
|
|
||||||
type smallRect struct {
|
|
||||||
Left, Top, Right, Bottom int16
|
|
||||||
}
|
|
||||||
|
|
||||||
type consoleScreenBufferInfo struct {
|
|
||||||
DwSize coord
|
|
||||||
DwCursorPosition coord
|
|
||||||
WAttributes uint16
|
|
||||||
SrWindow smallRect
|
|
||||||
DwMaximumWindowSize coord
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConsoleScreenBufferInfo(hConsoleOutput uintptr) *consoleScreenBufferInfo {
|
|
||||||
var csbi consoleScreenBufferInfo
|
|
||||||
ret, _, _ := procGetConsoleScreenBufferInfo.Call(
|
|
||||||
hConsoleOutput,
|
|
||||||
uintptr(unsafe.Pointer(&csbi)))
|
|
||||||
if ret == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &csbi
|
|
||||||
}
|
|
||||||
|
|
||||||
func setConsoleTextAttribute(hConsoleOutput uintptr, wAttributes uint16) bool {
|
|
||||||
ret, _, _ := procSetConsoleTextAttribute.Call(
|
|
||||||
hConsoleOutput,
|
|
||||||
uintptr(wAttributes))
|
|
||||||
return ret != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type textAttributes struct {
|
|
||||||
foregroundColor uint16
|
|
||||||
backgroundColor uint16
|
|
||||||
foregroundIntensity uint16
|
|
||||||
backgroundIntensity uint16
|
|
||||||
underscore uint16
|
|
||||||
otherAttributes uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertTextAttr(winAttr uint16) *textAttributes {
|
|
||||||
fgColor := winAttr & (foregroundRed | foregroundGreen | foregroundBlue)
|
|
||||||
bgColor := winAttr & (backgroundRed | backgroundGreen | backgroundBlue)
|
|
||||||
fgIntensity := winAttr & foregroundIntensity
|
|
||||||
bgIntensity := winAttr & backgroundIntensity
|
|
||||||
underline := winAttr & underscore
|
|
||||||
otherAttributes := winAttr &^ (foregroundMask | backgroundMask | underscore)
|
|
||||||
return &textAttributes{fgColor, bgColor, fgIntensity, bgIntensity, underline, otherAttributes}
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertWinAttr(textAttr *textAttributes) uint16 {
|
|
||||||
var winAttr uint16
|
|
||||||
winAttr |= textAttr.foregroundColor
|
|
||||||
winAttr |= textAttr.backgroundColor
|
|
||||||
winAttr |= textAttr.foregroundIntensity
|
|
||||||
winAttr |= textAttr.backgroundIntensity
|
|
||||||
winAttr |= textAttr.underscore
|
|
||||||
winAttr |= textAttr.otherAttributes
|
|
||||||
return winAttr
|
|
||||||
}
|
|
||||||
|
|
||||||
func changeColor(param []byte) parseResult {
|
|
||||||
screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout))
|
|
||||||
if screenInfo == nil {
|
|
||||||
return noConsole
|
|
||||||
}
|
|
||||||
|
|
||||||
winAttr := convertTextAttr(screenInfo.WAttributes)
|
|
||||||
strParam := string(param)
|
|
||||||
if len(strParam) <= 0 {
|
|
||||||
strParam = "0"
|
|
||||||
}
|
|
||||||
csiParam := strings.Split(strParam, string(separatorChar))
|
|
||||||
for _, p := range csiParam {
|
|
||||||
c, ok := colorMap[p]
|
|
||||||
switch {
|
|
||||||
case !ok:
|
|
||||||
switch p {
|
|
||||||
case ansiReset:
|
|
||||||
winAttr.foregroundColor = defaultAttr.foregroundColor
|
|
||||||
winAttr.backgroundColor = defaultAttr.backgroundColor
|
|
||||||
winAttr.foregroundIntensity = defaultAttr.foregroundIntensity
|
|
||||||
winAttr.backgroundIntensity = defaultAttr.backgroundIntensity
|
|
||||||
winAttr.underscore = 0
|
|
||||||
winAttr.otherAttributes = 0
|
|
||||||
case ansiIntensityOn:
|
|
||||||
winAttr.foregroundIntensity = foregroundIntensity
|
|
||||||
case ansiIntensityOff:
|
|
||||||
winAttr.foregroundIntensity = 0
|
|
||||||
case ansiUnderlineOn:
|
|
||||||
winAttr.underscore = underscore
|
|
||||||
case ansiUnderlineOff:
|
|
||||||
winAttr.underscore = 0
|
|
||||||
case ansiBlinkOn:
|
|
||||||
winAttr.backgroundIntensity = backgroundIntensity
|
|
||||||
case ansiBlinkOff:
|
|
||||||
winAttr.backgroundIntensity = 0
|
|
||||||
default:
|
|
||||||
// unknown code
|
|
||||||
}
|
|
||||||
case c.drawType == foreground:
|
|
||||||
winAttr.foregroundColor = c.code
|
|
||||||
case c.drawType == background:
|
|
||||||
winAttr.backgroundColor = c.code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
winTextAttribute := convertWinAttr(winAttr)
|
|
||||||
setConsoleTextAttribute(uintptr(syscall.Stdout), winTextAttribute)
|
|
||||||
|
|
||||||
return changedColor
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseEscapeSequence(command byte, param []byte) parseResult {
|
|
||||||
if defaultAttr == nil {
|
|
||||||
return noConsole
|
|
||||||
}
|
|
||||||
|
|
||||||
switch command {
|
|
||||||
case sgrCode:
|
|
||||||
return changeColor(param)
|
|
||||||
default:
|
|
||||||
return unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cw *ansiColorWriter) flushBuffer() (int, error) {
|
|
||||||
return cw.flushTo(cw.w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cw *ansiColorWriter) resetBuffer() (int, error) {
|
|
||||||
return cw.flushTo(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cw *ansiColorWriter) flushTo(w io.Writer) (int, error) {
|
|
||||||
var n1, n2 int
|
|
||||||
var err error
|
|
||||||
|
|
||||||
startBytes := cw.paramStartBuf.Bytes()
|
|
||||||
cw.paramStartBuf.Reset()
|
|
||||||
if w != nil {
|
|
||||||
n1, err = cw.w.Write(startBytes)
|
|
||||||
if err != nil {
|
|
||||||
return n1, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
n1 = len(startBytes)
|
|
||||||
}
|
|
||||||
paramBytes := cw.paramBuf.Bytes()
|
|
||||||
cw.paramBuf.Reset()
|
|
||||||
if w != nil {
|
|
||||||
n2, err = cw.w.Write(paramBytes)
|
|
||||||
if err != nil {
|
|
||||||
return n1 + n2, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
n2 = len(paramBytes)
|
|
||||||
}
|
|
||||||
return n1 + n2, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isParameterChar(b byte) bool {
|
|
||||||
return ('0' <= b && b <= '9') || b == separatorChar
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cw *ansiColorWriter) Write(p []byte) (int, error) {
|
|
||||||
var r, nw, first, last int
|
|
||||||
if cw.mode != DiscardNonColorEscSeq {
|
|
||||||
cw.state = outsideCsiCode
|
|
||||||
cw.resetBuffer()
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
for i, ch := range p {
|
|
||||||
switch cw.state {
|
|
||||||
case outsideCsiCode:
|
|
||||||
if ch == firstCsiChar {
|
|
||||||
cw.paramStartBuf.WriteByte(ch)
|
|
||||||
cw.state = firstCsiCode
|
|
||||||
}
|
|
||||||
case firstCsiCode:
|
|
||||||
switch ch {
|
|
||||||
case firstCsiChar:
|
|
||||||
cw.paramStartBuf.WriteByte(ch)
|
|
||||||
break
|
|
||||||
case secondeCsiChar:
|
|
||||||
cw.paramStartBuf.WriteByte(ch)
|
|
||||||
cw.state = secondCsiCode
|
|
||||||
last = i - 1
|
|
||||||
default:
|
|
||||||
cw.resetBuffer()
|
|
||||||
cw.state = outsideCsiCode
|
|
||||||
}
|
|
||||||
case secondCsiCode:
|
|
||||||
if isParameterChar(ch) {
|
|
||||||
cw.paramBuf.WriteByte(ch)
|
|
||||||
} else {
|
|
||||||
nw, err = cw.w.Write(p[first:last])
|
|
||||||
r += nw
|
|
||||||
if err != nil {
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
first = i + 1
|
|
||||||
result := parseEscapeSequence(ch, cw.paramBuf.Bytes())
|
|
||||||
if result == noConsole || (cw.mode == OutputNonColorEscSeq && result == unknown) {
|
|
||||||
cw.paramBuf.WriteByte(ch)
|
|
||||||
nw, err := cw.flushBuffer()
|
|
||||||
if err != nil {
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
r += nw
|
|
||||||
} else {
|
|
||||||
n, _ := cw.resetBuffer()
|
|
||||||
// Add one more to the size of the buffer for the last ch
|
|
||||||
r += n + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cw.state = outsideCsiCode
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
cw.state = outsideCsiCode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cw.mode != DiscardNonColorEscSeq || cw.state == outsideCsiCode {
|
|
||||||
nw, err = cw.w.Write(p[first:])
|
|
||||||
r += nw
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, err
|
|
||||||
}
|
|
@ -1,294 +0,0 @@
|
|||||||
// Copyright 2014 beego Author. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package logs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"syscall"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var GetConsoleScreenBufferInfo = getConsoleScreenBufferInfo
|
|
||||||
|
|
||||||
func ChangeColor(color uint16) {
|
|
||||||
setConsoleTextAttribute(uintptr(syscall.Stdout), color)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResetColor() {
|
|
||||||
ChangeColor(uint16(0x0007))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWritePlanText(t *testing.T) {
|
|
||||||
inner := bytes.NewBufferString("")
|
|
||||||
w := NewAnsiColorWriter(inner)
|
|
||||||
expected := "plain text"
|
|
||||||
fmt.Fprintf(w, expected)
|
|
||||||
actual := inner.String()
|
|
||||||
if actual != expected {
|
|
||||||
t.Errorf("Get %q, want %q", actual, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteParseText(t *testing.T) {
|
|
||||||
inner := bytes.NewBufferString("")
|
|
||||||
w := NewAnsiColorWriter(inner)
|
|
||||||
|
|
||||||
inputTail := "\x1b[0mtail text"
|
|
||||||
expectedTail := "tail text"
|
|
||||||
fmt.Fprintf(w, inputTail)
|
|
||||||
actualTail := inner.String()
|
|
||||||
inner.Reset()
|
|
||||||
if actualTail != expectedTail {
|
|
||||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
|
||||||
}
|
|
||||||
|
|
||||||
inputHead := "head text\x1b[0m"
|
|
||||||
expectedHead := "head text"
|
|
||||||
fmt.Fprintf(w, inputHead)
|
|
||||||
actualHead := inner.String()
|
|
||||||
inner.Reset()
|
|
||||||
if actualHead != expectedHead {
|
|
||||||
t.Errorf("Get %q, want %q", actualHead, expectedHead)
|
|
||||||
}
|
|
||||||
|
|
||||||
inputBothEnds := "both ends \x1b[0m text"
|
|
||||||
expectedBothEnds := "both ends text"
|
|
||||||
fmt.Fprintf(w, inputBothEnds)
|
|
||||||
actualBothEnds := inner.String()
|
|
||||||
inner.Reset()
|
|
||||||
if actualBothEnds != expectedBothEnds {
|
|
||||||
t.Errorf("Get %q, want %q", actualBothEnds, expectedBothEnds)
|
|
||||||
}
|
|
||||||
|
|
||||||
inputManyEsc := "\x1b\x1b\x1b\x1b[0m many esc"
|
|
||||||
expectedManyEsc := "\x1b\x1b\x1b many esc"
|
|
||||||
fmt.Fprintf(w, inputManyEsc)
|
|
||||||
actualManyEsc := inner.String()
|
|
||||||
inner.Reset()
|
|
||||||
if actualManyEsc != expectedManyEsc {
|
|
||||||
t.Errorf("Get %q, want %q", actualManyEsc, expectedManyEsc)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedSplit := "split text"
|
|
||||||
for _, ch := range "split \x1b[0m text" {
|
|
||||||
fmt.Fprintf(w, string(ch))
|
|
||||||
}
|
|
||||||
actualSplit := inner.String()
|
|
||||||
inner.Reset()
|
|
||||||
if actualSplit != expectedSplit {
|
|
||||||
t.Errorf("Get %q, want %q", actualSplit, expectedSplit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type screenNotFoundError struct {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeAnsiColor(expectedText, colorCode string) (actualText string, actualAttributes uint16, err error) {
|
|
||||||
inner := bytes.NewBufferString("")
|
|
||||||
w := NewAnsiColorWriter(inner)
|
|
||||||
fmt.Fprintf(w, "\x1b[%sm%s", colorCode, expectedText)
|
|
||||||
|
|
||||||
actualText = inner.String()
|
|
||||||
screenInfo := GetConsoleScreenBufferInfo(uintptr(syscall.Stdout))
|
|
||||||
if screenInfo != nil {
|
|
||||||
actualAttributes = screenInfo.WAttributes
|
|
||||||
} else {
|
|
||||||
err = &screenNotFoundError{}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type testParam struct {
|
|
||||||
text string
|
|
||||||
attributes uint16
|
|
||||||
ansiColor string
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteAnsiColorText(t *testing.T) {
|
|
||||||
screenInfo := GetConsoleScreenBufferInfo(uintptr(syscall.Stdout))
|
|
||||||
if screenInfo == nil {
|
|
||||||
t.Fatal("Could not get ConsoleScreenBufferInfo")
|
|
||||||
}
|
|
||||||
defer ChangeColor(screenInfo.WAttributes)
|
|
||||||
defaultFgColor := screenInfo.WAttributes & uint16(0x0007)
|
|
||||||
defaultBgColor := screenInfo.WAttributes & uint16(0x0070)
|
|
||||||
defaultFgIntensity := screenInfo.WAttributes & uint16(0x0008)
|
|
||||||
defaultBgIntensity := screenInfo.WAttributes & uint16(0x0080)
|
|
||||||
|
|
||||||
fgParam := []testParam{
|
|
||||||
{"foreground black ", uint16(0x0000 | 0x0000), "30"},
|
|
||||||
{"foreground red ", uint16(0x0004 | 0x0000), "31"},
|
|
||||||
{"foreground green ", uint16(0x0002 | 0x0000), "32"},
|
|
||||||
{"foreground yellow ", uint16(0x0006 | 0x0000), "33"},
|
|
||||||
{"foreground blue ", uint16(0x0001 | 0x0000), "34"},
|
|
||||||
{"foreground magenta", uint16(0x0005 | 0x0000), "35"},
|
|
||||||
{"foreground cyan ", uint16(0x0003 | 0x0000), "36"},
|
|
||||||
{"foreground white ", uint16(0x0007 | 0x0000), "37"},
|
|
||||||
{"foreground default", defaultFgColor | 0x0000, "39"},
|
|
||||||
{"foreground light gray ", uint16(0x0000 | 0x0008 | 0x0000), "90"},
|
|
||||||
{"foreground light red ", uint16(0x0004 | 0x0008 | 0x0000), "91"},
|
|
||||||
{"foreground light green ", uint16(0x0002 | 0x0008 | 0x0000), "92"},
|
|
||||||
{"foreground light yellow ", uint16(0x0006 | 0x0008 | 0x0000), "93"},
|
|
||||||
{"foreground light blue ", uint16(0x0001 | 0x0008 | 0x0000), "94"},
|
|
||||||
{"foreground light magenta", uint16(0x0005 | 0x0008 | 0x0000), "95"},
|
|
||||||
{"foreground light cyan ", uint16(0x0003 | 0x0008 | 0x0000), "96"},
|
|
||||||
{"foreground light white ", uint16(0x0007 | 0x0008 | 0x0000), "97"},
|
|
||||||
}
|
|
||||||
|
|
||||||
bgParam := []testParam{
|
|
||||||
{"background black ", uint16(0x0007 | 0x0000), "40"},
|
|
||||||
{"background red ", uint16(0x0007 | 0x0040), "41"},
|
|
||||||
{"background green ", uint16(0x0007 | 0x0020), "42"},
|
|
||||||
{"background yellow ", uint16(0x0007 | 0x0060), "43"},
|
|
||||||
{"background blue ", uint16(0x0007 | 0x0010), "44"},
|
|
||||||
{"background magenta", uint16(0x0007 | 0x0050), "45"},
|
|
||||||
{"background cyan ", uint16(0x0007 | 0x0030), "46"},
|
|
||||||
{"background white ", uint16(0x0007 | 0x0070), "47"},
|
|
||||||
{"background default", uint16(0x0007) | defaultBgColor, "49"},
|
|
||||||
{"background light gray ", uint16(0x0007 | 0x0000 | 0x0080), "100"},
|
|
||||||
{"background light red ", uint16(0x0007 | 0x0040 | 0x0080), "101"},
|
|
||||||
{"background light green ", uint16(0x0007 | 0x0020 | 0x0080), "102"},
|
|
||||||
{"background light yellow ", uint16(0x0007 | 0x0060 | 0x0080), "103"},
|
|
||||||
{"background light blue ", uint16(0x0007 | 0x0010 | 0x0080), "104"},
|
|
||||||
{"background light magenta", uint16(0x0007 | 0x0050 | 0x0080), "105"},
|
|
||||||
{"background light cyan ", uint16(0x0007 | 0x0030 | 0x0080), "106"},
|
|
||||||
{"background light white ", uint16(0x0007 | 0x0070 | 0x0080), "107"},
|
|
||||||
}
|
|
||||||
|
|
||||||
resetParam := []testParam{
|
|
||||||
{"all reset", defaultFgColor | defaultBgColor | defaultFgIntensity | defaultBgIntensity, "0"},
|
|
||||||
{"all reset", defaultFgColor | defaultBgColor | defaultFgIntensity | defaultBgIntensity, ""},
|
|
||||||
}
|
|
||||||
|
|
||||||
boldParam := []testParam{
|
|
||||||
{"bold on", uint16(0x0007 | 0x0008), "1"},
|
|
||||||
{"bold off", uint16(0x0007), "21"},
|
|
||||||
}
|
|
||||||
|
|
||||||
underscoreParam := []testParam{
|
|
||||||
{"underscore on", uint16(0x0007 | 0x8000), "4"},
|
|
||||||
{"underscore off", uint16(0x0007), "24"},
|
|
||||||
}
|
|
||||||
|
|
||||||
blinkParam := []testParam{
|
|
||||||
{"blink on", uint16(0x0007 | 0x0080), "5"},
|
|
||||||
{"blink off", uint16(0x0007), "25"},
|
|
||||||
}
|
|
||||||
|
|
||||||
mixedParam := []testParam{
|
|
||||||
{"both black, bold, underline, blink", uint16(0x0000 | 0x0000 | 0x0008 | 0x8000 | 0x0080), "30;40;1;4;5"},
|
|
||||||
{"both red, bold, underline, blink", uint16(0x0004 | 0x0040 | 0x0008 | 0x8000 | 0x0080), "31;41;1;4;5"},
|
|
||||||
{"both green, bold, underline, blink", uint16(0x0002 | 0x0020 | 0x0008 | 0x8000 | 0x0080), "32;42;1;4;5"},
|
|
||||||
{"both yellow, bold, underline, blink", uint16(0x0006 | 0x0060 | 0x0008 | 0x8000 | 0x0080), "33;43;1;4;5"},
|
|
||||||
{"both blue, bold, underline, blink", uint16(0x0001 | 0x0010 | 0x0008 | 0x8000 | 0x0080), "34;44;1;4;5"},
|
|
||||||
{"both magenta, bold, underline, blink", uint16(0x0005 | 0x0050 | 0x0008 | 0x8000 | 0x0080), "35;45;1;4;5"},
|
|
||||||
{"both cyan, bold, underline, blink", uint16(0x0003 | 0x0030 | 0x0008 | 0x8000 | 0x0080), "36;46;1;4;5"},
|
|
||||||
{"both white, bold, underline, blink", uint16(0x0007 | 0x0070 | 0x0008 | 0x8000 | 0x0080), "37;47;1;4;5"},
|
|
||||||
{"both default, bold, underline, blink", uint16(defaultFgColor | defaultBgColor | 0x0008 | 0x8000 | 0x0080), "39;49;1;4;5"},
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTextAttribute := func(expectedText string, expectedAttributes uint16, ansiColor string) {
|
|
||||||
actualText, actualAttributes, err := writeAnsiColor(expectedText, ansiColor)
|
|
||||||
if actualText != expectedText {
|
|
||||||
t.Errorf("Get %q, want %q", actualText, expectedText)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Could not get ConsoleScreenBufferInfo")
|
|
||||||
}
|
|
||||||
if actualAttributes != expectedAttributes {
|
|
||||||
t.Errorf("Text: %q, Get 0x%04x, want 0x%04x", expectedText, actualAttributes, expectedAttributes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range fgParam {
|
|
||||||
ResetColor()
|
|
||||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range bgParam {
|
|
||||||
ChangeColor(uint16(0x0070 | 0x0007))
|
|
||||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range resetParam {
|
|
||||||
ChangeColor(uint16(0x0000 | 0x0070 | 0x0008))
|
|
||||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
ResetColor()
|
|
||||||
for _, v := range boldParam {
|
|
||||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
ResetColor()
|
|
||||||
for _, v := range underscoreParam {
|
|
||||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
ResetColor()
|
|
||||||
for _, v := range blinkParam {
|
|
||||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range mixedParam {
|
|
||||||
ResetColor()
|
|
||||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIgnoreUnknownSequences(t *testing.T) {
|
|
||||||
inner := bytes.NewBufferString("")
|
|
||||||
w := NewModeAnsiColorWriter(inner, OutputNonColorEscSeq)
|
|
||||||
|
|
||||||
inputText := "\x1b[=decpath mode"
|
|
||||||
expectedTail := inputText
|
|
||||||
fmt.Fprintf(w, inputText)
|
|
||||||
actualTail := inner.String()
|
|
||||||
inner.Reset()
|
|
||||||
if actualTail != expectedTail {
|
|
||||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
|
||||||
}
|
|
||||||
|
|
||||||
inputText = "\x1b[=tailing esc and bracket\x1b["
|
|
||||||
expectedTail = inputText
|
|
||||||
fmt.Fprintf(w, inputText)
|
|
||||||
actualTail = inner.String()
|
|
||||||
inner.Reset()
|
|
||||||
if actualTail != expectedTail {
|
|
||||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
|
||||||
}
|
|
||||||
|
|
||||||
inputText = "\x1b[?tailing esc\x1b"
|
|
||||||
expectedTail = inputText
|
|
||||||
fmt.Fprintf(w, inputText)
|
|
||||||
actualTail = inner.String()
|
|
||||||
inner.Reset()
|
|
||||||
if actualTail != expectedTail {
|
|
||||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
|
||||||
}
|
|
||||||
|
|
||||||
inputText = "\x1b[1h;3punended color code invalid\x1b3"
|
|
||||||
expectedTail = inputText
|
|
||||||
fmt.Fprintf(w, inputText)
|
|
||||||
actualTail = inner.String()
|
|
||||||
inner.Reset()
|
|
||||||
if actualTail != expectedTail {
|
|
||||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
|
||||||
}
|
|
||||||
}
|
|
@ -63,7 +63,7 @@ func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error {
|
|||||||
defer c.innerWriter.Close()
|
defer c.innerWriter.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
c.lg.println(when, msg)
|
c.lg.writeln(when, msg)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,8 +17,10 @@ package logs
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/shiena/ansicolor"
|
||||||
)
|
)
|
||||||
|
|
||||||
// brush is a color join function
|
// brush is a color join function
|
||||||
@ -54,9 +56,9 @@ type consoleWriter struct {
|
|||||||
// NewConsole create ConsoleWriter returning as LoggerInterface.
|
// NewConsole create ConsoleWriter returning as LoggerInterface.
|
||||||
func NewConsole() Logger {
|
func NewConsole() Logger {
|
||||||
cw := &consoleWriter{
|
cw := &consoleWriter{
|
||||||
lg: newLogWriter(os.Stdout),
|
lg: newLogWriter(ansicolor.NewAnsiColorWriter(os.Stdout)),
|
||||||
Level: LevelDebug,
|
Level: LevelDebug,
|
||||||
Colorful: runtime.GOOS != "windows",
|
Colorful: true,
|
||||||
}
|
}
|
||||||
return cw
|
return cw
|
||||||
}
|
}
|
||||||
@ -67,11 +69,7 @@ func (c *consoleWriter) Init(jsonConfig string) error {
|
|||||||
if len(jsonConfig) == 0 {
|
if len(jsonConfig) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err := json.Unmarshal([]byte(jsonConfig), c)
|
return json.Unmarshal([]byte(jsonConfig), c)
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
c.Colorful = false
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMsg write message in console.
|
// WriteMsg write message in console.
|
||||||
@ -80,9 +78,9 @@ func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if c.Colorful {
|
if c.Colorful {
|
||||||
msg = colors[level](msg)
|
msg = strings.Replace(msg, levelPrefix[level], colors[level](levelPrefix[level]), 1)
|
||||||
}
|
}
|
||||||
c.lg.println(when, msg)
|
c.lg.writeln(when, msg)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/OwnLocal/goes"
|
||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
"github.com/belogik/goes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewES return a LoggerInterface
|
// NewES return a LoggerInterface
|
||||||
@ -21,7 +21,7 @@ func NewES() logs.Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type esLogger struct {
|
type esLogger struct {
|
||||||
*goes.Connection
|
*goes.Client
|
||||||
DSN string `json:"dsn"`
|
DSN string `json:"dsn"`
|
||||||
Level int `json:"level"`
|
Level int `json:"level"`
|
||||||
}
|
}
|
||||||
@ -41,8 +41,8 @@ func (el *esLogger) Init(jsonconfig string) error {
|
|||||||
} else if host, port, err := net.SplitHostPort(u.Host); err != nil {
|
} else if host, port, err := net.SplitHostPort(u.Host); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
conn := goes.NewConnection(host, port)
|
conn := goes.NewClient(host, port)
|
||||||
el.Connection = conn
|
el.Client = conn
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -78,3 +78,4 @@ func (el *esLogger) Flush() {
|
|||||||
func init() {
|
func init() {
|
||||||
logs.Register(logs.AdapterEs, NewES)
|
logs.Register(logs.AdapterEs, NewES)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
logs/log.go
12
logs/log.go
@ -47,7 +47,7 @@ import (
|
|||||||
|
|
||||||
// RFC5424 log message levels.
|
// RFC5424 log message levels.
|
||||||
const (
|
const (
|
||||||
LevelEmergency = iota
|
LevelEmergency = iota
|
||||||
LevelAlert
|
LevelAlert
|
||||||
LevelCritical
|
LevelCritical
|
||||||
LevelError
|
LevelError
|
||||||
@ -92,7 +92,7 @@ type Logger interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var adapters = make(map[string]newLoggerFunc)
|
var adapters = make(map[string]newLoggerFunc)
|
||||||
var levelPrefix = [LevelDebug + 1]string{"[M] ", "[A] ", "[C] ", "[E] ", "[W] ", "[N] ", "[I] ", "[D] "}
|
var levelPrefix = [LevelDebug + 1]string{"[M]", "[A]", "[C]", "[E]", "[W]", "[N]", "[I]", "[D]"}
|
||||||
|
|
||||||
// Register makes a log provide available by the provided name.
|
// Register makes a log provide available by the provided name.
|
||||||
// If Register is called twice with the same name or if driver is nil,
|
// If Register is called twice with the same name or if driver is nil,
|
||||||
@ -187,12 +187,12 @@ func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log, ok := adapters[adapterName]
|
logAdapter, ok := adapters[adapterName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||||
}
|
}
|
||||||
|
|
||||||
lg := log()
|
lg := logAdapter()
|
||||||
err := lg.Init(config)
|
err := lg.Init(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
|
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
|
||||||
@ -248,7 +248,7 @@ func (bl *BeeLogger) Write(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
// writeMsg will always add a '\n' character
|
// writeMsg will always add a '\n' character
|
||||||
if p[len(p)-1] == '\n' {
|
if p[len(p)-1] == '\n' {
|
||||||
p = p[0: len(p)-1]
|
p = p[0 : len(p)-1]
|
||||||
}
|
}
|
||||||
// set levelLoggerImpl to ensure all log message will be write out
|
// set levelLoggerImpl to ensure all log message will be write out
|
||||||
err = bl.writeMsg(levelLoggerImpl, string(p))
|
err = bl.writeMsg(levelLoggerImpl, string(p))
|
||||||
@ -287,7 +287,7 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
|
|||||||
// set to emergency to ensure all log will be print out correctly
|
// set to emergency to ensure all log will be print out correctly
|
||||||
logLevel = LevelEmergency
|
logLevel = LevelEmergency
|
||||||
} else {
|
} else {
|
||||||
msg = levelPrefix[logLevel] + msg
|
msg = levelPrefix[logLevel] + " " + msg
|
||||||
}
|
}
|
||||||
|
|
||||||
if bl.asynchronous {
|
if bl.asynchronous {
|
||||||
|
127
logs/logger.go
127
logs/logger.go
@ -15,9 +15,8 @@
|
|||||||
package logs
|
package logs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -31,47 +30,13 @@ func newLogWriter(wr io.Writer) *logWriter {
|
|||||||
return &logWriter{writer: wr}
|
return &logWriter{writer: wr}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lg *logWriter) println(when time.Time, msg string) {
|
func (lg *logWriter) writeln(when time.Time, msg string) {
|
||||||
lg.Lock()
|
lg.Lock()
|
||||||
h, _, _:= formatTimeHeader(when)
|
h, _, _ := formatTimeHeader(when)
|
||||||
lg.writer.Write(append(append(h, msg...), '\n'))
|
lg.writer.Write(append(append(h, msg...), '\n'))
|
||||||
lg.Unlock()
|
lg.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
type outputMode int
|
|
||||||
|
|
||||||
// DiscardNonColorEscSeq supports the divided color escape sequence.
|
|
||||||
// But non-color escape sequence is not output.
|
|
||||||
// Please use the OutputNonColorEscSeq If you want to output a non-color
|
|
||||||
// escape sequences such as ncurses. However, it does not support the divided
|
|
||||||
// color escape sequence.
|
|
||||||
const (
|
|
||||||
_ outputMode = iota
|
|
||||||
DiscardNonColorEscSeq
|
|
||||||
OutputNonColorEscSeq
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewAnsiColorWriter creates and initializes a new ansiColorWriter
|
|
||||||
// using io.Writer w as its initial contents.
|
|
||||||
// In the console of Windows, which change the foreground and background
|
|
||||||
// colors of the text by the escape sequence.
|
|
||||||
// In the console of other systems, which writes to w all text.
|
|
||||||
func NewAnsiColorWriter(w io.Writer) io.Writer {
|
|
||||||
return NewModeAnsiColorWriter(w, DiscardNonColorEscSeq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewModeAnsiColorWriter create and initializes a new ansiColorWriter
|
|
||||||
// by specifying the outputMode.
|
|
||||||
func NewModeAnsiColorWriter(w io.Writer, mode outputMode) io.Writer {
|
|
||||||
if _, ok := w.(*ansiColorWriter); !ok {
|
|
||||||
return &ansiColorWriter{
|
|
||||||
w: w,
|
|
||||||
mode: mode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
y1 = `0123456789`
|
y1 = `0123456789`
|
||||||
y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||||
@ -146,63 +111,65 @@ var (
|
|||||||
reset = string([]byte{27, 91, 48, 109})
|
reset = string([]byte{27, 91, 48, 109})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var once sync.Once
|
||||||
|
var colorMap map[string]string
|
||||||
|
|
||||||
|
func initColor() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
green = w32Green
|
||||||
|
white = w32White
|
||||||
|
yellow = w32Yellow
|
||||||
|
red = w32Red
|
||||||
|
blue = w32Blue
|
||||||
|
magenta = w32Magenta
|
||||||
|
cyan = w32Cyan
|
||||||
|
}
|
||||||
|
colorMap = map[string]string{
|
||||||
|
//by color
|
||||||
|
"green": green,
|
||||||
|
"white": white,
|
||||||
|
"yellow": yellow,
|
||||||
|
"red": red,
|
||||||
|
//by method
|
||||||
|
"GET": blue,
|
||||||
|
"POST": cyan,
|
||||||
|
"PUT": yellow,
|
||||||
|
"DELETE": red,
|
||||||
|
"PATCH": green,
|
||||||
|
"HEAD": magenta,
|
||||||
|
"OPTIONS": white,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ColorByStatus return color by http code
|
// ColorByStatus return color by http code
|
||||||
// 2xx return Green
|
// 2xx return Green
|
||||||
// 3xx return White
|
// 3xx return White
|
||||||
// 4xx return Yellow
|
// 4xx return Yellow
|
||||||
// 5xx return Red
|
// 5xx return Red
|
||||||
func ColorByStatus(cond bool, code int) string {
|
func ColorByStatus(code int) string {
|
||||||
|
once.Do(initColor)
|
||||||
switch {
|
switch {
|
||||||
case code >= 200 && code < 300:
|
case code >= 200 && code < 300:
|
||||||
return map[bool]string{true: green, false: w32Green}[cond]
|
return colorMap["green"]
|
||||||
case code >= 300 && code < 400:
|
case code >= 300 && code < 400:
|
||||||
return map[bool]string{true: white, false: w32White}[cond]
|
return colorMap["white"]
|
||||||
case code >= 400 && code < 500:
|
case code >= 400 && code < 500:
|
||||||
return map[bool]string{true: yellow, false: w32Yellow}[cond]
|
return colorMap["yellow"]
|
||||||
default:
|
default:
|
||||||
return map[bool]string{true: red, false: w32Red}[cond]
|
return colorMap["red"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ColorByMethod return color by http code
|
// ColorByMethod return color by http code
|
||||||
// GET return Blue
|
func ColorByMethod(method string) string {
|
||||||
// POST return Cyan
|
once.Do(initColor)
|
||||||
// PUT return Yellow
|
if c := colorMap[method]; c != "" {
|
||||||
// DELETE return Red
|
return c
|
||||||
// PATCH return Green
|
|
||||||
// HEAD return Magenta
|
|
||||||
// OPTIONS return WHITE
|
|
||||||
func ColorByMethod(cond bool, method string) string {
|
|
||||||
switch method {
|
|
||||||
case "GET":
|
|
||||||
return map[bool]string{true: blue, false: w32Blue}[cond]
|
|
||||||
case "POST":
|
|
||||||
return map[bool]string{true: cyan, false: w32Cyan}[cond]
|
|
||||||
case "PUT":
|
|
||||||
return map[bool]string{true: yellow, false: w32Yellow}[cond]
|
|
||||||
case "DELETE":
|
|
||||||
return map[bool]string{true: red, false: w32Red}[cond]
|
|
||||||
case "PATCH":
|
|
||||||
return map[bool]string{true: green, false: w32Green}[cond]
|
|
||||||
case "HEAD":
|
|
||||||
return map[bool]string{true: magenta, false: w32Magenta}[cond]
|
|
||||||
case "OPTIONS":
|
|
||||||
return map[bool]string{true: white, false: w32White}[cond]
|
|
||||||
default:
|
|
||||||
return reset
|
|
||||||
}
|
}
|
||||||
|
return reset
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guard Mutex to guarantee atomic of W32Debug(string) function
|
// ResetColor return reset color
|
||||||
var mu sync.Mutex
|
func ResetColor() string {
|
||||||
|
return reset
|
||||||
// W32Debug Helper method to output colored logs in Windows terminals
|
|
||||||
func W32Debug(msg string) {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
|
|
||||||
current := time.Now()
|
|
||||||
w := NewAnsiColorWriter(os.Stdout)
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "[beego] %v %s\n", current.Format("2006/01/02 - 15:04:05"), msg)
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
package logs
|
package logs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -56,20 +55,3 @@ func TestFormatHeader_1(t *testing.T) {
|
|||||||
tm = tm.Add(dur)
|
tm = tm.Add(dur)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewAnsiColor1(t *testing.T) {
|
|
||||||
inner := bytes.NewBufferString("")
|
|
||||||
w := NewAnsiColorWriter(inner)
|
|
||||||
if w == inner {
|
|
||||||
t.Errorf("Get %#v, want %#v", w, inner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewAnsiColor2(t *testing.T) {
|
|
||||||
inner := bytes.NewBufferString("")
|
|
||||||
w1 := NewAnsiColorWriter(inner)
|
|
||||||
w2 := NewAnsiColorWriter(w1)
|
|
||||||
if w1 != w2 {
|
|
||||||
t.Errorf("Get %#v, want %#v", w1, w2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -17,7 +17,7 @@ package migration
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego/logs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Index struct defines the structure of Index Columns
|
// Index struct defines the structure of Index Columns
|
||||||
@ -316,7 +316,7 @@ func (m *Migration) GetSQL() (sql string) {
|
|||||||
sql += fmt.Sprintf("ALTER TABLE `%s` ", m.TableName)
|
sql += fmt.Sprintf("ALTER TABLE `%s` ", m.TableName)
|
||||||
for index, column := range m.Columns {
|
for index, column := range m.Columns {
|
||||||
if !column.remove {
|
if !column.remove {
|
||||||
beego.BeeLogger.Info("col")
|
logs.Info("col")
|
||||||
sql += fmt.Sprintf("\n ADD `%s` %s %s %s %s %s", column.Name, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
|
sql += fmt.Sprintf("\n ADD `%s` %s %s %s %s %s", column.Name, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
|
||||||
} else {
|
} else {
|
||||||
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
|
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
|
||||||
|
@ -176,8 +176,9 @@ func Register(name string, m Migrationer) error {
|
|||||||
func Upgrade(lasttime int64) error {
|
func Upgrade(lasttime int64) error {
|
||||||
sm := sortMap(migrationMap)
|
sm := sortMap(migrationMap)
|
||||||
i := 0
|
i := 0
|
||||||
|
migs, _ := getAllMigrations()
|
||||||
for _, v := range sm {
|
for _, v := range sm {
|
||||||
if v.created > lasttime {
|
if _, ok := migs[v.name]; !ok {
|
||||||
logs.Info("start upgrade", v.name)
|
logs.Info("start upgrade", v.name)
|
||||||
v.m.Reset()
|
v.m.Reset()
|
||||||
v.m.Up()
|
v.m.Up()
|
||||||
@ -310,3 +311,20 @@ func isRollBack(name string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
func getAllMigrations() (map[string]string, error) {
|
||||||
|
o := orm.NewOrm()
|
||||||
|
var maps []orm.Params
|
||||||
|
migs := make(map[string]string)
|
||||||
|
num, err := o.Raw("select * from migrations order by id_migration desc").Values(&maps)
|
||||||
|
if err != nil {
|
||||||
|
logs.Info("get name has error", err)
|
||||||
|
return migs, err
|
||||||
|
}
|
||||||
|
if num > 0 {
|
||||||
|
for _, v := range maps {
|
||||||
|
name := v["name"].(string)
|
||||||
|
migs[name] = v["status"].(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return migs, nil
|
||||||
|
}
|
||||||
|
@ -207,11 +207,11 @@ func (n *Namespace) Include(cList ...ControllerInterface) *Namespace {
|
|||||||
func (n *Namespace) Namespace(ns ...*Namespace) *Namespace {
|
func (n *Namespace) Namespace(ns ...*Namespace) *Namespace {
|
||||||
for _, ni := range ns {
|
for _, ni := range ns {
|
||||||
for k, v := range ni.handlers.routers {
|
for k, v := range ni.handlers.routers {
|
||||||
if t, ok := n.handlers.routers[k]; ok {
|
if _, ok := n.handlers.routers[k]; ok {
|
||||||
addPrefix(v, ni.prefix)
|
addPrefix(v, ni.prefix)
|
||||||
n.handlers.routers[k].AddTree(ni.prefix, v)
|
n.handlers.routers[k].AddTree(ni.prefix, v)
|
||||||
} else {
|
} else {
|
||||||
t = NewTree()
|
t := NewTree()
|
||||||
t.AddTree(ni.prefix, v)
|
t.AddTree(ni.prefix, v)
|
||||||
addPrefix(t, ni.prefix)
|
addPrefix(t, ni.prefix)
|
||||||
n.handlers.routers[k] = t
|
n.handlers.routers[k] = t
|
||||||
@ -236,11 +236,11 @@ func (n *Namespace) Namespace(ns ...*Namespace) *Namespace {
|
|||||||
func AddNamespace(nl ...*Namespace) {
|
func AddNamespace(nl ...*Namespace) {
|
||||||
for _, n := range nl {
|
for _, n := range nl {
|
||||||
for k, v := range n.handlers.routers {
|
for k, v := range n.handlers.routers {
|
||||||
if t, ok := BeeApp.Handlers.routers[k]; ok {
|
if _, ok := BeeApp.Handlers.routers[k]; ok {
|
||||||
addPrefix(v, n.prefix)
|
addPrefix(v, n.prefix)
|
||||||
BeeApp.Handlers.routers[k].AddTree(n.prefix, v)
|
BeeApp.Handlers.routers[k].AddTree(n.prefix, v)
|
||||||
} else {
|
} else {
|
||||||
t = NewTree()
|
t := NewTree()
|
||||||
t.AddTree(n.prefix, v)
|
t.AddTree(n.prefix, v)
|
||||||
addPrefix(t, n.prefix)
|
addPrefix(t, n.prefix)
|
||||||
BeeApp.Handlers.routers[k] = t
|
BeeApp.Handlers.routers[k] = t
|
||||||
|
@ -198,7 +198,7 @@ func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
|
|||||||
column = strings.Replace(column, "%COL%", fi.column, -1)
|
column = strings.Replace(column, "%COL%", fi.column, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.description != "" {
|
if fi.description != "" && al.Driver!=DRSqlite {
|
||||||
column += " " + fmt.Sprintf("COMMENT '%s'",fi.description)
|
column += " " + fmt.Sprintf("COMMENT '%s'",fi.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
67
orm/db.go
67
orm/db.go
@ -621,6 +621,31 @@ func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var findAutoNowAdd, findAutoNow bool
|
||||||
|
var index int
|
||||||
|
for i, col := range setNames {
|
||||||
|
if mi.fields.GetByColumn(col).autoNowAdd {
|
||||||
|
index = i
|
||||||
|
findAutoNowAdd = true
|
||||||
|
}
|
||||||
|
if mi.fields.GetByColumn(col).autoNow {
|
||||||
|
findAutoNow = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if findAutoNowAdd {
|
||||||
|
setNames = append(setNames[0:index], setNames[index+1:]...)
|
||||||
|
setValues = append(setValues[0:index], setValues[index+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !findAutoNow {
|
||||||
|
for col, info := range mi.fields.columns {
|
||||||
|
if info.autoNow {
|
||||||
|
setNames = append(setNames, col)
|
||||||
|
setValues = append(setValues, time.Now())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setValues = append(setValues, pkValue)
|
setValues = append(setValues, pkValue)
|
||||||
|
|
||||||
Q := d.ins.TableQuote()
|
Q := d.ins.TableQuote()
|
||||||
@ -762,7 +787,13 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
d.ins.ReplaceMarks(&query)
|
d.ins.ReplaceMarks(&query)
|
||||||
res, err := q.Exec(query, values...)
|
var err error
|
||||||
|
var res sql.Result
|
||||||
|
if qs != nil && qs.forContext {
|
||||||
|
res, err = q.ExecContext(qs.ctx, query, values...)
|
||||||
|
} else {
|
||||||
|
res, err = q.Exec(query, values...)
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return res.RowsAffected()
|
return res.RowsAffected()
|
||||||
}
|
}
|
||||||
@ -851,11 +882,16 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
|
|||||||
for i := range marks {
|
for i := range marks {
|
||||||
marks[i] = "?"
|
marks[i] = "?"
|
||||||
}
|
}
|
||||||
sql := fmt.Sprintf("IN (%s)", strings.Join(marks, ", "))
|
sqlIn := fmt.Sprintf("IN (%s)", strings.Join(marks, ", "))
|
||||||
query = fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s %s", Q, mi.table, Q, Q, mi.fields.pk.column, Q, sql)
|
query = fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s %s", Q, mi.table, Q, Q, mi.fields.pk.column, Q, sqlIn)
|
||||||
|
|
||||||
d.ins.ReplaceMarks(&query)
|
d.ins.ReplaceMarks(&query)
|
||||||
res, err := q.Exec(query, args...)
|
var res sql.Result
|
||||||
|
if qs != nil && qs.forContext {
|
||||||
|
res, err = q.ExecContext(qs.ctx, query, args...)
|
||||||
|
} else {
|
||||||
|
res, err = q.Exec(query, args...)
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
num, err := res.RowsAffected()
|
num, err := res.RowsAffected()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -978,11 +1014,18 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
|
|||||||
d.ins.ReplaceMarks(&query)
|
d.ins.ReplaceMarks(&query)
|
||||||
|
|
||||||
var rs *sql.Rows
|
var rs *sql.Rows
|
||||||
r, err := q.Query(query, args...)
|
var err error
|
||||||
if err != nil {
|
if qs != nil && qs.forContext {
|
||||||
return 0, err
|
rs, err = q.QueryContext(qs.ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rs, err = q.Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rs = r
|
|
||||||
|
|
||||||
refs := make([]interface{}, colsNum)
|
refs := make([]interface{}, colsNum)
|
||||||
for i := range refs {
|
for i := range refs {
|
||||||
@ -1111,8 +1154,12 @@ func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition
|
|||||||
|
|
||||||
d.ins.ReplaceMarks(&query)
|
d.ins.ReplaceMarks(&query)
|
||||||
|
|
||||||
row := q.QueryRow(query, args...)
|
var row *sql.Row
|
||||||
|
if qs != nil && qs.forContext {
|
||||||
|
row = q.QueryRowContext(qs.ctx, query, args...)
|
||||||
|
} else {
|
||||||
|
row = q.QueryRow(query, args...)
|
||||||
|
}
|
||||||
err = row.Scan(&cnt)
|
err = row.Scan(&cnt)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
103
orm/db_alias.go
103
orm/db_alias.go
@ -15,6 +15,7 @@
|
|||||||
package orm
|
package orm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -103,6 +104,96 @@ func (ac *_dbCache) getDefault() (al *alias) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
*sync.RWMutex
|
||||||
|
DB *sql.DB
|
||||||
|
stmts map[string]*sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) Begin() (*sql.Tx, error) {
|
||||||
|
return d.DB.Begin()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
|
||||||
|
return d.DB.BeginTx(ctx, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) getStmt(query string) (*sql.Stmt, error) {
|
||||||
|
d.RLock()
|
||||||
|
if stmt, ok := d.stmts[query]; ok {
|
||||||
|
d.RUnlock()
|
||||||
|
return stmt, nil
|
||||||
|
}
|
||||||
|
d.RUnlock()
|
||||||
|
|
||||||
|
stmt, err := d.Prepare(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d.Lock()
|
||||||
|
d.stmts[query] = stmt
|
||||||
|
d.Unlock()
|
||||||
|
return stmt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) Prepare(query string) (*sql.Stmt, error) {
|
||||||
|
return d.DB.Prepare(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
||||||
|
return d.DB.PrepareContext(ctx, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
stmt, err := d.getStmt(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return stmt.Exec(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
stmt, err := d.getStmt(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return stmt.ExecContext(ctx, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
stmt, err := d.getStmt(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return stmt.Query(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
stmt, err := d.getStmt(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return stmt.QueryContext(ctx, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||||
|
stmt, err := d.getStmt(query)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return stmt.QueryRow(args...)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
||||||
|
|
||||||
|
stmt, err := d.getStmt(query)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return stmt.QueryRowContext(ctx, args)
|
||||||
|
}
|
||||||
|
|
||||||
type alias struct {
|
type alias struct {
|
||||||
Name string
|
Name string
|
||||||
Driver DriverType
|
Driver DriverType
|
||||||
@ -110,7 +201,7 @@ type alias struct {
|
|||||||
DataSource string
|
DataSource string
|
||||||
MaxIdleConns int
|
MaxIdleConns int
|
||||||
MaxOpenConns int
|
MaxOpenConns int
|
||||||
DB *sql.DB
|
DB *DB
|
||||||
DbBaser dbBaser
|
DbBaser dbBaser
|
||||||
TZ *time.Location
|
TZ *time.Location
|
||||||
Engine string
|
Engine string
|
||||||
@ -176,7 +267,11 @@ func addAliasWthDB(aliasName, driverName string, db *sql.DB) (*alias, error) {
|
|||||||
al := new(alias)
|
al := new(alias)
|
||||||
al.Name = aliasName
|
al.Name = aliasName
|
||||||
al.DriverName = driverName
|
al.DriverName = driverName
|
||||||
al.DB = db
|
al.DB = &DB{
|
||||||
|
RWMutex: new(sync.RWMutex),
|
||||||
|
DB: db,
|
||||||
|
stmts: make(map[string]*sql.Stmt),
|
||||||
|
}
|
||||||
|
|
||||||
if dr, ok := drivers[driverName]; ok {
|
if dr, ok := drivers[driverName]; ok {
|
||||||
al.DbBaser = dbBasers[dr]
|
al.DbBaser = dbBasers[dr]
|
||||||
@ -272,7 +367,7 @@ func SetDataBaseTZ(aliasName string, tz *time.Location) error {
|
|||||||
func SetMaxIdleConns(aliasName string, maxIdleConns int) {
|
func SetMaxIdleConns(aliasName string, maxIdleConns int) {
|
||||||
al := getDbAlias(aliasName)
|
al := getDbAlias(aliasName)
|
||||||
al.MaxIdleConns = maxIdleConns
|
al.MaxIdleConns = maxIdleConns
|
||||||
al.DB.SetMaxIdleConns(maxIdleConns)
|
al.DB.DB.SetMaxIdleConns(maxIdleConns)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name
|
// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name
|
||||||
@ -296,7 +391,7 @@ func GetDB(aliasNames ...string) (*sql.DB, error) {
|
|||||||
}
|
}
|
||||||
al, ok := dataBaseCache.get(name)
|
al, ok := dataBaseCache.get(name)
|
||||||
if ok {
|
if ok {
|
||||||
return al.DB, nil
|
return al.DB.DB, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("DataBase of alias name `%s` not found", name)
|
return nil, fmt.Errorf("DataBase of alias name `%s` not found", name)
|
||||||
}
|
}
|
||||||
|
@ -335,11 +335,11 @@ func RegisterModelWithSuffix(suffix string, models ...interface{}) {
|
|||||||
// BootStrap bootstrap models.
|
// BootStrap bootstrap models.
|
||||||
// make all model parsed and can not add more models
|
// make all model parsed and can not add more models
|
||||||
func BootStrap() {
|
func BootStrap() {
|
||||||
|
modelCache.Lock()
|
||||||
|
defer modelCache.Unlock()
|
||||||
if modelCache.done {
|
if modelCache.done {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
modelCache.Lock()
|
|
||||||
defer modelCache.Unlock()
|
|
||||||
bootStrap()
|
bootStrap()
|
||||||
modelCache.done = true
|
modelCache.done = true
|
||||||
}
|
}
|
||||||
|
@ -301,7 +301,7 @@ checkType:
|
|||||||
fi.sf = sf
|
fi.sf = sf
|
||||||
fi.fullName = mi.fullName + mName + "." + sf.Name
|
fi.fullName = mi.fullName + mName + "." + sf.Name
|
||||||
|
|
||||||
fi.description = sf.Tag.Get("description")
|
fi.description = tags["description"]
|
||||||
fi.null = attrs["null"]
|
fi.null = attrs["null"]
|
||||||
fi.index = attrs["index"]
|
fi.index = attrs["index"]
|
||||||
fi.auto = attrs["auto"]
|
fi.auto = attrs["auto"]
|
||||||
|
@ -44,6 +44,7 @@ var supportTag = map[string]int{
|
|||||||
"decimals": 2,
|
"decimals": 2,
|
||||||
"on_delete": 2,
|
"on_delete": 2,
|
||||||
"type": 2,
|
"type": 2,
|
||||||
|
"description": 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
// get reflect.Type name with package path.
|
// get reflect.Type name with package path.
|
||||||
@ -65,7 +66,7 @@ func getTableName(val reflect.Value) string {
|
|||||||
return snakeString(reflect.Indirect(val).Type().Name())
|
return snakeString(reflect.Indirect(val).Type().Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
// get table engine, mysiam or innodb.
|
// get table engine, myisam or innodb.
|
||||||
func getTableEngine(val reflect.Value) string {
|
func getTableEngine(val reflect.Value) string {
|
||||||
fun := val.MethodByName("TableEngine")
|
fun := val.MethodByName("TableEngine")
|
||||||
if fun.IsValid() {
|
if fun.IsValid() {
|
||||||
@ -109,7 +110,7 @@ func getTableUnique(val reflect.Value) [][]string {
|
|||||||
func getColumnName(ft int, addrField reflect.Value, sf reflect.StructField, col string) string {
|
func getColumnName(ft int, addrField reflect.Value, sf reflect.StructField, col string) string {
|
||||||
column := col
|
column := col
|
||||||
if col == "" {
|
if col == "" {
|
||||||
column = snakeString(sf.Name)
|
column = nameStrategyMap[nameStrategy](sf.Name)
|
||||||
}
|
}
|
||||||
switch ft {
|
switch ft {
|
||||||
case RelForeignKey, RelOneToOne:
|
case RelForeignKey, RelOneToOne:
|
||||||
|
22
orm/orm.go
22
orm/orm.go
@ -60,6 +60,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ const (
|
|||||||
var (
|
var (
|
||||||
Debug = false
|
Debug = false
|
||||||
DebugLog = NewLog(os.Stdout)
|
DebugLog = NewLog(os.Stdout)
|
||||||
DefaultRowsLimit = 1000
|
DefaultRowsLimit = -1
|
||||||
DefaultRelsDepth = 2
|
DefaultRelsDepth = 2
|
||||||
DefaultTimeLoc = time.Local
|
DefaultTimeLoc = time.Local
|
||||||
ErrTxHasBegan = errors.New("<Ormer.Begin> transaction already begin")
|
ErrTxHasBegan = errors.New("<Ormer.Begin> transaction already begin")
|
||||||
@ -425,7 +426,7 @@ func (o *orm) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
|
|||||||
func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) {
|
func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) {
|
||||||
var name string
|
var name string
|
||||||
if table, ok := ptrStructOrTableName.(string); ok {
|
if table, ok := ptrStructOrTableName.(string); ok {
|
||||||
name = snakeString(table)
|
name = nameStrategyMap[defaultNameStrategy](table)
|
||||||
if mi, ok := modelCache.get(name); ok {
|
if mi, ok := modelCache.get(name); ok {
|
||||||
qs = newQuerySet(o, mi)
|
qs = newQuerySet(o, mi)
|
||||||
}
|
}
|
||||||
@ -522,6 +523,15 @@ func (o *orm) Driver() Driver {
|
|||||||
return driver(o.alias.Name)
|
return driver(o.alias.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return sql.DBStats for current database
|
||||||
|
func (o *orm) DBStats() *sql.DBStats {
|
||||||
|
if o.alias != nil && o.alias.DB != nil {
|
||||||
|
stats := o.alias.DB.DB.Stats()
|
||||||
|
return &stats
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewOrm create new orm
|
// NewOrm create new orm
|
||||||
func NewOrm() Ormer {
|
func NewOrm() Ormer {
|
||||||
BootStrap() // execute only once
|
BootStrap() // execute only once
|
||||||
@ -548,8 +558,12 @@ func NewOrmWithDB(driverName, aliasName string, db *sql.DB) (Ormer, error) {
|
|||||||
|
|
||||||
al.Name = aliasName
|
al.Name = aliasName
|
||||||
al.DriverName = driverName
|
al.DriverName = driverName
|
||||||
al.DB = db
|
al.DB = &DB{
|
||||||
|
RWMutex: new(sync.RWMutex),
|
||||||
|
DB: db,
|
||||||
|
stmts: make(map[string]*sql.Stmt),
|
||||||
|
}
|
||||||
|
|
||||||
detectTZ(al)
|
detectTZ(al)
|
||||||
|
|
||||||
o := new(orm)
|
o := new(orm)
|
||||||
|
@ -29,6 +29,9 @@ type Log struct {
|
|||||||
*log.Logger
|
*log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//costomer log func
|
||||||
|
var LogFunc func(query map[string]interface{})
|
||||||
|
|
||||||
// NewLog set io.Writer to create a Logger.
|
// NewLog set io.Writer to create a Logger.
|
||||||
func NewLog(out io.Writer) *Log {
|
func NewLog(out io.Writer) *Log {
|
||||||
d := new(Log)
|
d := new(Log)
|
||||||
@ -37,12 +40,15 @@ func NewLog(out io.Writer) *Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error, args ...interface{}) {
|
func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error, args ...interface{}) {
|
||||||
|
var logMap = make(map[string]interface{})
|
||||||
sub := time.Now().Sub(t) / 1e5
|
sub := time.Now().Sub(t) / 1e5
|
||||||
elsp := float64(int(sub)) / 10.0
|
elsp := float64(int(sub)) / 10.0
|
||||||
|
logMap["cost_time"] = elsp
|
||||||
flag := " OK"
|
flag := " OK"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
flag = "FAIL"
|
flag = "FAIL"
|
||||||
}
|
}
|
||||||
|
logMap["flag"] = flag
|
||||||
con := fmt.Sprintf(" -[Queries/%s] - [%s / %11s / %7.1fms] - [%s]", alias.Name, flag, operaton, elsp, query)
|
con := fmt.Sprintf(" -[Queries/%s] - [%s / %11s / %7.1fms] - [%s]", alias.Name, flag, operaton, elsp, query)
|
||||||
cons := make([]string, 0, len(args))
|
cons := make([]string, 0, len(args))
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
@ -54,6 +60,10 @@ func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
con += " - " + err.Error()
|
con += " - " + err.Error()
|
||||||
}
|
}
|
||||||
|
logMap["sql"] = fmt.Sprintf("%s-`%s`", query, strings.Join(cons, "`, `"))
|
||||||
|
if LogFunc != nil{
|
||||||
|
LogFunc(logMap)
|
||||||
|
}
|
||||||
DebugLog.Println(con)
|
DebugLog.Println(con)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +133,13 @@ func (d *dbQueryLog) Prepare(query string) (*sql.Stmt, error) {
|
|||||||
return stmt, err
|
return stmt, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
||||||
|
a := time.Now()
|
||||||
|
stmt, err := d.db.PrepareContext(ctx, query)
|
||||||
|
debugLogQueies(d.alias, "db.Prepare", query, a, err)
|
||||||
|
return stmt, err
|
||||||
|
}
|
||||||
|
|
||||||
func (d *dbQueryLog) Exec(query string, args ...interface{}) (sql.Result, error) {
|
func (d *dbQueryLog) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||||
a := time.Now()
|
a := time.Now()
|
||||||
res, err := d.db.Exec(query, args...)
|
res, err := d.db.Exec(query, args...)
|
||||||
@ -130,6 +147,13 @@ func (d *dbQueryLog) Exec(query string, args ...interface{}) (sql.Result, error)
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
a := time.Now()
|
||||||
|
res, err := d.db.ExecContext(ctx, query, args...)
|
||||||
|
debugLogQueies(d.alias, "db.Exec", query, a, err, args...)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
func (d *dbQueryLog) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
func (d *dbQueryLog) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
a := time.Now()
|
a := time.Now()
|
||||||
res, err := d.db.Query(query, args...)
|
res, err := d.db.Query(query, args...)
|
||||||
@ -137,6 +161,13 @@ func (d *dbQueryLog) Query(query string, args ...interface{}) (*sql.Rows, error)
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
a := time.Now()
|
||||||
|
res, err := d.db.QueryContext(ctx, query, args...)
|
||||||
|
debugLogQueies(d.alias, "db.Query", query, a, err, args...)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
func (d *dbQueryLog) QueryRow(query string, args ...interface{}) *sql.Row {
|
func (d *dbQueryLog) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||||
a := time.Now()
|
a := time.Now()
|
||||||
res := d.db.QueryRow(query, args...)
|
res := d.db.QueryRow(query, args...)
|
||||||
@ -144,6 +175,13 @@ func (d *dbQueryLog) QueryRow(query string, args ...interface{}) *sql.Row {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
||||||
|
a := time.Now()
|
||||||
|
res := d.db.QueryRowContext(ctx, query, args...)
|
||||||
|
debugLogQueies(d.alias, "db.QueryRow", query, a, nil, args...)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func (d *dbQueryLog) Begin() (*sql.Tx, error) {
|
func (d *dbQueryLog) Begin() (*sql.Tx, error) {
|
||||||
a := time.Now()
|
a := time.Now()
|
||||||
tx, err := d.db.(txer).Begin()
|
tx, err := d.db.(txer).Begin()
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package orm
|
package orm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,17 +56,19 @@ func ColValue(opt operator, value interface{}) interface{} {
|
|||||||
|
|
||||||
// real query struct
|
// real query struct
|
||||||
type querySet struct {
|
type querySet struct {
|
||||||
mi *modelInfo
|
mi *modelInfo
|
||||||
cond *Condition
|
cond *Condition
|
||||||
related []string
|
related []string
|
||||||
relDepth int
|
relDepth int
|
||||||
limit int64
|
limit int64
|
||||||
offset int64
|
offset int64
|
||||||
groups []string
|
groups []string
|
||||||
orders []string
|
orders []string
|
||||||
distinct bool
|
distinct bool
|
||||||
forupdate bool
|
forupdate bool
|
||||||
orm *orm
|
orm *orm
|
||||||
|
ctx context.Context
|
||||||
|
forContext bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ QuerySeter = new(querySet)
|
var _ QuerySeter = new(querySet)
|
||||||
@ -275,6 +278,13 @@ func (o *querySet) RowsToStruct(ptrStruct interface{}, keyCol, valueCol string)
|
|||||||
panic(ErrNotImplement)
|
panic(ErrNotImplement)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set context to QuerySeter.
|
||||||
|
func (o querySet) WithContext(ctx context.Context) QuerySeter {
|
||||||
|
o.ctx = ctx
|
||||||
|
o.forContext = true
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
// create new QuerySeter.
|
// create new QuerySeter.
|
||||||
func newQuerySet(orm *orm, mi *modelInfo) QuerySeter {
|
func newQuerySet(orm *orm, mi *modelInfo) QuerySeter {
|
||||||
o := new(querySet)
|
o := new(querySet)
|
||||||
|
@ -150,8 +150,10 @@ func (o *rawSet) setFieldValue(ind reflect.Value, value interface{}) {
|
|||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
if value == nil {
|
if value == nil {
|
||||||
ind.Set(reflect.Zero(ind.Type()))
|
ind.Set(reflect.Zero(ind.Type()))
|
||||||
|
return
|
||||||
} else if _, ok := ind.Interface().(time.Time); ok {
|
}
|
||||||
|
switch ind.Interface().(type) {
|
||||||
|
case time.Time:
|
||||||
var str string
|
var str string
|
||||||
switch d := value.(type) {
|
switch d := value.(type) {
|
||||||
case time.Time:
|
case time.Time:
|
||||||
@ -178,7 +180,25 @@ func (o *rawSet) setFieldValue(ind reflect.Value, value interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case sql.NullString, sql.NullInt64, sql.NullFloat64, sql.NullBool:
|
||||||
|
indi := reflect.New(ind.Type()).Interface()
|
||||||
|
sc, ok := indi.(sql.Scanner)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := sc.Scan(value)
|
||||||
|
if err == nil {
|
||||||
|
ind.Set(reflect.Indirect(reflect.ValueOf(sc)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
if value == nil {
|
||||||
|
ind.Set(reflect.Zero(ind.Type()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ind.Set(reflect.New(ind.Type().Elem()))
|
||||||
|
o.setFieldValue(reflect.Indirect(ind), value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,7 +378,7 @@ func (o *rawSet) QueryRow(containers ...interface{}) error {
|
|||||||
_, tags := parseStructTag(fe.Tag.Get(defaultStructTagName))
|
_, tags := parseStructTag(fe.Tag.Get(defaultStructTagName))
|
||||||
var col string
|
var col string
|
||||||
if col = tags["column"]; col == "" {
|
if col = tags["column"]; col == "" {
|
||||||
col = snakeString(fe.Name)
|
col = nameStrategyMap[nameStrategy](fe.Name)
|
||||||
}
|
}
|
||||||
if v, ok := columnsMp[col]; ok {
|
if v, ok := columnsMp[col]; ok {
|
||||||
value := reflect.ValueOf(v).Elem().Interface()
|
value := reflect.ValueOf(v).Elem().Interface()
|
||||||
@ -509,7 +529,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) {
|
|||||||
_, tags := parseStructTag(fe.Tag.Get(defaultStructTagName))
|
_, tags := parseStructTag(fe.Tag.Get(defaultStructTagName))
|
||||||
var col string
|
var col string
|
||||||
if col = tags["column"]; col == "" {
|
if col = tags["column"]; col == "" {
|
||||||
col = snakeString(fe.Name)
|
col = nameStrategyMap[nameStrategy](fe.Name)
|
||||||
}
|
}
|
||||||
if v, ok := columnsMp[col]; ok {
|
if v, ok := columnsMp[col]; ok {
|
||||||
value := reflect.ValueOf(v).Elem().Interface()
|
value := reflect.ValueOf(v).Elem().Interface()
|
||||||
|
@ -458,6 +458,15 @@ func TestNullDataTypes(t *testing.T) {
|
|||||||
throwFail(t, AssertIs((*d.TimePtr).UTC().Format(testTime), timePtr.UTC().Format(testTime)))
|
throwFail(t, AssertIs((*d.TimePtr).UTC().Format(testTime), timePtr.UTC().Format(testTime)))
|
||||||
throwFail(t, AssertIs((*d.DatePtr).UTC().Format(testDate), datePtr.UTC().Format(testDate)))
|
throwFail(t, AssertIs((*d.DatePtr).UTC().Format(testDate), datePtr.UTC().Format(testDate)))
|
||||||
throwFail(t, AssertIs((*d.DateTimePtr).UTC().Format(testDateTime), dateTimePtr.UTC().Format(testDateTime)))
|
throwFail(t, AssertIs((*d.DateTimePtr).UTC().Format(testDateTime), dateTimePtr.UTC().Format(testDateTime)))
|
||||||
|
|
||||||
|
// test support for pointer fields using RawSeter.QueryRows()
|
||||||
|
var dnList []*DataNull
|
||||||
|
Q := dDbBaser.TableQuote()
|
||||||
|
num, err = dORM.Raw(fmt.Sprintf("SELECT * FROM %sdata_null%s where id=?", Q, Q), 3).QueryRows(&dnList)
|
||||||
|
throwFailNow(t, err)
|
||||||
|
throwFailNow(t, AssertIs(num, 1))
|
||||||
|
equal := reflect.DeepEqual(*dnList[0], d)
|
||||||
|
throwFailNow(t, AssertIs(equal, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDataCustomTypes(t *testing.T) {
|
func TestDataCustomTypes(t *testing.T) {
|
||||||
@ -1679,6 +1688,31 @@ func TestRawQueryRow(t *testing.T) {
|
|||||||
throwFail(t, AssertIs(uid, 4))
|
throwFail(t, AssertIs(uid, 4))
|
||||||
throwFail(t, AssertIs(*status, 3))
|
throwFail(t, AssertIs(*status, 3))
|
||||||
throwFail(t, AssertIs(pid, nil))
|
throwFail(t, AssertIs(pid, nil))
|
||||||
|
|
||||||
|
// test for sql.Null* fields
|
||||||
|
nData := &DataNull{
|
||||||
|
NullString: sql.NullString{String: "test sql.null", Valid: true},
|
||||||
|
NullBool: sql.NullBool{Bool: true, Valid: true},
|
||||||
|
NullInt64: sql.NullInt64{Int64: 42, Valid: true},
|
||||||
|
NullFloat64: sql.NullFloat64{Float64: 42.42, Valid: true},
|
||||||
|
}
|
||||||
|
newId, err := dORM.Insert(nData)
|
||||||
|
throwFailNow(t, err)
|
||||||
|
|
||||||
|
var nd *DataNull
|
||||||
|
query = fmt.Sprintf("SELECT * FROM %sdata_null%s where id=?", Q, Q)
|
||||||
|
err = dORM.Raw(query, newId).QueryRow(&nd)
|
||||||
|
throwFailNow(t, err)
|
||||||
|
|
||||||
|
throwFailNow(t, AssertNot(nd, nil))
|
||||||
|
throwFail(t, AssertIs(nd.NullBool.Valid, true))
|
||||||
|
throwFail(t, AssertIs(nd.NullBool.Bool, true))
|
||||||
|
throwFail(t, AssertIs(nd.NullString.Valid, true))
|
||||||
|
throwFail(t, AssertIs(nd.NullString.String, "test sql.null"))
|
||||||
|
throwFail(t, AssertIs(nd.NullInt64.Valid, true))
|
||||||
|
throwFail(t, AssertIs(nd.NullInt64.Int64, 42))
|
||||||
|
throwFail(t, AssertIs(nd.NullFloat64.Valid, true))
|
||||||
|
throwFail(t, AssertIs(nd.NullFloat64.Float64, 42.42))
|
||||||
}
|
}
|
||||||
|
|
||||||
// user_profile table
|
// user_profile table
|
||||||
@ -1771,6 +1805,32 @@ func TestQueryRows(t *testing.T) {
|
|||||||
throwFailNow(t, AssertIs(l[1].UserName, "astaxie"))
|
throwFailNow(t, AssertIs(l[1].UserName, "astaxie"))
|
||||||
throwFailNow(t, AssertIs(l[1].Age, 30))
|
throwFailNow(t, AssertIs(l[1].Age, 30))
|
||||||
|
|
||||||
|
// test for sql.Null* fields
|
||||||
|
nData := &DataNull{
|
||||||
|
NullString: sql.NullString{String: "test sql.null", Valid: true},
|
||||||
|
NullBool: sql.NullBool{Bool: true, Valid: true},
|
||||||
|
NullInt64: sql.NullInt64{Int64: 42, Valid: true},
|
||||||
|
NullFloat64: sql.NullFloat64{Float64: 42.42, Valid: true},
|
||||||
|
}
|
||||||
|
newId, err := dORM.Insert(nData)
|
||||||
|
throwFailNow(t, err)
|
||||||
|
|
||||||
|
var nDataList []*DataNull
|
||||||
|
query = fmt.Sprintf("SELECT * FROM %sdata_null%s where id=?", Q, Q)
|
||||||
|
num, err = dORM.Raw(query, newId).QueryRows(&nDataList)
|
||||||
|
throwFailNow(t, err)
|
||||||
|
throwFailNow(t, AssertIs(num, 1))
|
||||||
|
|
||||||
|
nd := nDataList[0]
|
||||||
|
throwFailNow(t, AssertNot(nd, nil))
|
||||||
|
throwFail(t, AssertIs(nd.NullBool.Valid, true))
|
||||||
|
throwFail(t, AssertIs(nd.NullBool.Bool, true))
|
||||||
|
throwFail(t, AssertIs(nd.NullString.Valid, true))
|
||||||
|
throwFail(t, AssertIs(nd.NullString.String, "test sql.null"))
|
||||||
|
throwFail(t, AssertIs(nd.NullInt64.Valid, true))
|
||||||
|
throwFail(t, AssertIs(nd.NullInt64.Int64, 42))
|
||||||
|
throwFail(t, AssertIs(nd.NullFloat64.Valid, true))
|
||||||
|
throwFail(t, AssertIs(nd.NullFloat64.Float64, 42.42))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRawValues(t *testing.T) {
|
func TestRawValues(t *testing.T) {
|
||||||
|
10
orm/types.go
10
orm/types.go
@ -55,7 +55,7 @@ type Ormer interface {
|
|||||||
// for example:
|
// for example:
|
||||||
// user := new(User)
|
// user := new(User)
|
||||||
// id, err = Ormer.Insert(user)
|
// id, err = Ormer.Insert(user)
|
||||||
// user must a pointer and Insert will set user's pk field
|
// user must be a pointer and Insert will set user's pk field
|
||||||
Insert(interface{}) (int64, error)
|
Insert(interface{}) (int64, error)
|
||||||
// mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value")
|
// mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value")
|
||||||
// if colu type is integer : can use(+-*/), string : convert(colu,"value")
|
// if colu type is integer : can use(+-*/), string : convert(colu,"value")
|
||||||
@ -128,6 +128,7 @@ type Ormer interface {
|
|||||||
// // update user testing's name to slene
|
// // update user testing's name to slene
|
||||||
Raw(query string, args ...interface{}) RawSeter
|
Raw(query string, args ...interface{}) RawSeter
|
||||||
Driver() Driver
|
Driver() Driver
|
||||||
|
DBStats() *sql.DBStats
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inserter insert prepared statement
|
// Inserter insert prepared statement
|
||||||
@ -395,16 +396,23 @@ type RawSeter interface {
|
|||||||
type stmtQuerier interface {
|
type stmtQuerier interface {
|
||||||
Close() error
|
Close() error
|
||||||
Exec(args ...interface{}) (sql.Result, error)
|
Exec(args ...interface{}) (sql.Result, error)
|
||||||
|
//ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error)
|
||||||
Query(args ...interface{}) (*sql.Rows, error)
|
Query(args ...interface{}) (*sql.Rows, error)
|
||||||
|
//QueryContext(args ...interface{}) (*sql.Rows, error)
|
||||||
QueryRow(args ...interface{}) *sql.Row
|
QueryRow(args ...interface{}) *sql.Row
|
||||||
|
//QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row
|
||||||
}
|
}
|
||||||
|
|
||||||
// db querier
|
// db querier
|
||||||
type dbQuerier interface {
|
type dbQuerier interface {
|
||||||
Prepare(query string) (*sql.Stmt, error)
|
Prepare(query string) (*sql.Stmt, error)
|
||||||
|
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
|
||||||
Exec(query string, args ...interface{}) (sql.Result, error)
|
Exec(query string, args ...interface{}) (sql.Result, error)
|
||||||
|
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
|
||||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||||
|
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
|
||||||
QueryRow(query string, args ...interface{}) *sql.Row
|
QueryRow(query string, args ...interface{}) *sql.Row
|
||||||
|
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
|
||||||
}
|
}
|
||||||
|
|
||||||
// type DB interface {
|
// type DB interface {
|
||||||
|
43
orm/utils.go
43
orm/utils.go
@ -23,6 +23,18 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type fn func(string) string
|
||||||
|
|
||||||
|
var (
|
||||||
|
nameStrategyMap = map[string]fn{
|
||||||
|
defaultNameStrategy: snakeString,
|
||||||
|
SnakeAcronymNameStrategy: snakeStringWithAcronym,
|
||||||
|
}
|
||||||
|
defaultNameStrategy = "snakeString"
|
||||||
|
SnakeAcronymNameStrategy = "snakeStringWithAcronym"
|
||||||
|
nameStrategy = defaultNameStrategy
|
||||||
|
)
|
||||||
|
|
||||||
// StrTo is the target string
|
// StrTo is the target string
|
||||||
type StrTo string
|
type StrTo string
|
||||||
|
|
||||||
@ -117,7 +129,7 @@ func (f StrTo) Uint16() (uint16, error) {
|
|||||||
return uint16(v), err
|
return uint16(v), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uint32 string to uint31
|
// Uint32 string to uint32
|
||||||
func (f StrTo) Uint32() (uint32, error) {
|
func (f StrTo) Uint32() (uint32, error) {
|
||||||
v, err := strconv.ParseUint(f.String(), 10, 32)
|
v, err := strconv.ParseUint(f.String(), 10, 32)
|
||||||
return uint32(v), err
|
return uint32(v), err
|
||||||
@ -198,6 +210,27 @@ func ToInt64(value interface{}) (d int64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func snakeStringWithAcronym(s string) string {
|
||||||
|
data := make([]byte, 0, len(s)*2)
|
||||||
|
num := len(s)
|
||||||
|
for i := 0; i < num; i++ {
|
||||||
|
d := s[i]
|
||||||
|
before := false
|
||||||
|
after := false
|
||||||
|
if i > 0 {
|
||||||
|
before = s[i-1] >= 'a' && s[i-1] <= 'z'
|
||||||
|
}
|
||||||
|
if i+1 < num {
|
||||||
|
after = s[i+1] >= 'a' && s[i+1] <= 'z'
|
||||||
|
}
|
||||||
|
if i > 0 && d >= 'A' && d <= 'Z' && (before || after) {
|
||||||
|
data = append(data, '_')
|
||||||
|
}
|
||||||
|
data = append(data, d)
|
||||||
|
}
|
||||||
|
return strings.ToLower(string(data[:]))
|
||||||
|
}
|
||||||
|
|
||||||
// snake string, XxYy to xx_yy , XxYY to xx_y_y
|
// snake string, XxYy to xx_yy , XxYY to xx_y_y
|
||||||
func snakeString(s string) string {
|
func snakeString(s string) string {
|
||||||
data := make([]byte, 0, len(s)*2)
|
data := make([]byte, 0, len(s)*2)
|
||||||
@ -216,6 +249,14 @@ func snakeString(s string) string {
|
|||||||
return strings.ToLower(string(data[:]))
|
return strings.ToLower(string(data[:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNameStrategy set different name strategy
|
||||||
|
func SetNameStrategy(s string) {
|
||||||
|
if SnakeAcronymNameStrategy != s {
|
||||||
|
nameStrategy = defaultNameStrategy
|
||||||
|
}
|
||||||
|
nameStrategy = s
|
||||||
|
}
|
||||||
|
|
||||||
// camel string, xx_yy to XxYy
|
// camel string, xx_yy to XxYy
|
||||||
func camelString(s string) string {
|
func camelString(s string) string {
|
||||||
data := make([]byte, 0, len(s))
|
data := make([]byte, 0, len(s))
|
||||||
|
@ -51,3 +51,20 @@ func TestSnakeString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSnakeStringWithAcronym(t *testing.T) {
|
||||||
|
camel := []string{"ID", "PicURL", "HelloWorld", "HelloWorld", "HelLOWord", "PicUrl1", "XyXX"}
|
||||||
|
snake := []string{"id", "pic_url", "hello_world", "hello_world", "hel_lo_word", "pic_url1", "xy_xx"}
|
||||||
|
|
||||||
|
answer := make(map[string]string)
|
||||||
|
for i, v := range camel {
|
||||||
|
answer[v] = snake[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range camel {
|
||||||
|
res := snakeStringWithAcronym(v)
|
||||||
|
if res != answer[v] {
|
||||||
|
t.Error("Unit Test Fail:", v, res, answer[v])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
17
parser.go
17
parser.go
@ -35,7 +35,7 @@ import (
|
|||||||
"github.com/astaxie/beego/utils"
|
"github.com/astaxie/beego/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var globalRouterTemplate = `package routers
|
var globalRouterTemplate = `package {{.routersDir}}
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
@ -459,13 +459,17 @@ func genRouterCode(pkgRealpath string) {
|
|||||||
imports := ""
|
imports := ""
|
||||||
if len(c.ImportComments) > 0 {
|
if len(c.ImportComments) > 0 {
|
||||||
for _, i := range c.ImportComments {
|
for _, i := range c.ImportComments {
|
||||||
|
var s string
|
||||||
if i.ImportAlias != "" {
|
if i.ImportAlias != "" {
|
||||||
imports += fmt.Sprintf(`
|
s = fmt.Sprintf(`
|
||||||
%s "%s"`, i.ImportAlias, i.ImportPath)
|
%s "%s"`, i.ImportAlias, i.ImportPath)
|
||||||
} else {
|
} else {
|
||||||
imports += fmt.Sprintf(`
|
s = fmt.Sprintf(`
|
||||||
"%s"`, i.ImportPath)
|
"%s"`, i.ImportPath)
|
||||||
}
|
}
|
||||||
|
if !strings.Contains(globalimport, s) {
|
||||||
|
imports += s
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,7 +494,7 @@ func genRouterCode(pkgRealpath string) {
|
|||||||
}`, filters)
|
}`, filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
globalimport = imports
|
globalimport += imports
|
||||||
|
|
||||||
globalinfo = globalinfo + `
|
globalinfo = globalinfo + `
|
||||||
beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"],
|
beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"],
|
||||||
@ -512,7 +516,9 @@ func genRouterCode(pkgRealpath string) {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
|
routersDir := AppConfig.DefaultString("routersdir", "routers")
|
||||||
content := strings.Replace(globalRouterTemplate, "{{.globalinfo}}", globalinfo, -1)
|
content := strings.Replace(globalRouterTemplate, "{{.globalinfo}}", globalinfo, -1)
|
||||||
|
content = strings.Replace(content, "{{.routersDir}}", routersDir, -1)
|
||||||
content = strings.Replace(content, "{{.globalimport}}", globalimport, -1)
|
content = strings.Replace(content, "{{.globalimport}}", globalimport, -1)
|
||||||
f.WriteString(content)
|
f.WriteString(content)
|
||||||
}
|
}
|
||||||
@ -570,7 +576,8 @@ func getpathTime(pkgRealpath string) (lastupdate int64, err error) {
|
|||||||
func getRouterDir(pkgRealpath string) string {
|
func getRouterDir(pkgRealpath string) string {
|
||||||
dir := filepath.Dir(pkgRealpath)
|
dir := filepath.Dir(pkgRealpath)
|
||||||
for {
|
for {
|
||||||
d := filepath.Join(dir, "routers")
|
routersDir := AppConfig.DefaultString("routersdir", "routers")
|
||||||
|
d := filepath.Join(dir, routersDir)
|
||||||
if utils.FileExists(d) {
|
if utils.FileExists(d) {
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
@ -72,8 +72,8 @@ import (
|
|||||||
// AppIDToAppSecret is used to get appsecret throw appid
|
// AppIDToAppSecret is used to get appsecret throw appid
|
||||||
type AppIDToAppSecret func(string) string
|
type AppIDToAppSecret func(string) string
|
||||||
|
|
||||||
// APIBaiscAuth use the basic appid/appkey as the AppIdToAppSecret
|
// APIBasicAuth use the basic appid/appkey as the AppIdToAppSecret
|
||||||
func APIBaiscAuth(appid, appkey string) beego.FilterFunc {
|
func APIBasicAuth(appid, appkey string) beego.FilterFunc {
|
||||||
ft := func(aid string) string {
|
ft := func(aid string) string {
|
||||||
if aid == appid {
|
if aid == appid {
|
||||||
return appkey
|
return appkey
|
||||||
@ -83,6 +83,11 @@ func APIBaiscAuth(appid, appkey string) beego.FilterFunc {
|
|||||||
return APISecretAuth(ft, 300)
|
return APISecretAuth(ft, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// APIBaiscAuth calls APIBasicAuth for previous callers
|
||||||
|
func APIBaiscAuth(appid, appkey string) beego.FilterFunc {
|
||||||
|
return APIBasicAuth(appid, appkey)
|
||||||
|
}
|
||||||
|
|
||||||
// APISecretAuth use AppIdToAppSecret verify and
|
// APISecretAuth use AppIdToAppSecret verify and
|
||||||
func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc {
|
func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc {
|
||||||
return func(ctx *context.Context) {
|
return func(ctx *context.Context) {
|
||||||
|
111
router.go
111
router.go
@ -15,12 +15,12 @@
|
|||||||
package beego
|
package beego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -133,14 +133,15 @@ type ControllerRegister struct {
|
|||||||
|
|
||||||
// NewControllerRegister returns a new ControllerRegister.
|
// NewControllerRegister returns a new ControllerRegister.
|
||||||
func NewControllerRegister() *ControllerRegister {
|
func NewControllerRegister() *ControllerRegister {
|
||||||
cr := &ControllerRegister{
|
return &ControllerRegister{
|
||||||
routers: make(map[string]*Tree),
|
routers: make(map[string]*Tree),
|
||||||
policies: make(map[string]*Tree),
|
policies: make(map[string]*Tree),
|
||||||
|
pool: sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return beecontext.NewContext()
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
cr.pool.New = func() interface{} {
|
|
||||||
return beecontext.NewContext()
|
|
||||||
}
|
|
||||||
return cr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add controller handler and pattern rules to ControllerRegister.
|
// Add controller handler and pattern rules to ControllerRegister.
|
||||||
@ -478,8 +479,7 @@ func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter Filter
|
|||||||
// add Filter into
|
// add Filter into
|
||||||
func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) (err error) {
|
func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) (err error) {
|
||||||
if pos < BeforeStatic || pos > FinishRouter {
|
if pos < BeforeStatic || pos > FinishRouter {
|
||||||
err = fmt.Errorf("can not find your filter position")
|
return errors.New("can not find your filter position")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
p.enableFilter = true
|
p.enableFilter = true
|
||||||
p.filters[pos] = append(p.filters[pos], mr)
|
p.filters[pos] = append(p.filters[pos], mr)
|
||||||
@ -509,10 +509,10 @@ func (p *ControllerRegister) URLFor(endpoint string, values ...interface{}) stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
controllName := strings.Join(paths[:len(paths)-1], "/")
|
controllerName := strings.Join(paths[:len(paths)-1], "/")
|
||||||
methodName := paths[len(paths)-1]
|
methodName := paths[len(paths)-1]
|
||||||
for m, t := range p.routers {
|
for m, t := range p.routers {
|
||||||
ok, url := p.geturl(t, "/", controllName, methodName, params, m)
|
ok, url := p.getURL(t, "/", controllerName, methodName, params, m)
|
||||||
if ok {
|
if ok {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
@ -520,17 +520,17 @@ func (p *ControllerRegister) URLFor(endpoint string, values ...interface{}) stri
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName string, params map[string]string, httpMethod string) (bool, string) {
|
func (p *ControllerRegister) getURL(t *Tree, url, controllerName, methodName string, params map[string]string, httpMethod string) (bool, string) {
|
||||||
for _, subtree := range t.fixrouters {
|
for _, subtree := range t.fixrouters {
|
||||||
u := path.Join(url, subtree.prefix)
|
u := path.Join(url, subtree.prefix)
|
||||||
ok, u := p.geturl(subtree, u, controllName, methodName, params, httpMethod)
|
ok, u := p.getURL(subtree, u, controllerName, methodName, params, httpMethod)
|
||||||
if ok {
|
if ok {
|
||||||
return ok, u
|
return ok, u
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.wildcard != nil {
|
if t.wildcard != nil {
|
||||||
u := path.Join(url, urlPlaceholder)
|
u := path.Join(url, urlPlaceholder)
|
||||||
ok, u := p.geturl(t.wildcard, u, controllName, methodName, params, httpMethod)
|
ok, u := p.getURL(t.wildcard, u, controllerName, methodName, params, httpMethod)
|
||||||
if ok {
|
if ok {
|
||||||
return ok, u
|
return ok, u
|
||||||
}
|
}
|
||||||
@ -538,7 +538,7 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin
|
|||||||
for _, l := range t.leaves {
|
for _, l := range t.leaves {
|
||||||
if c, ok := l.runObject.(*ControllerInfo); ok {
|
if c, ok := l.runObject.(*ControllerInfo); ok {
|
||||||
if c.routerType == routerTypeBeego &&
|
if c.routerType == routerTypeBeego &&
|
||||||
strings.HasSuffix(path.Join(c.controllerType.PkgPath(), c.controllerType.Name()), controllName) {
|
strings.HasSuffix(path.Join(c.controllerType.PkgPath(), c.controllerType.Name()), controllerName) {
|
||||||
find := false
|
find := false
|
||||||
if HTTPMETHOD[strings.ToUpper(methodName)] {
|
if HTTPMETHOD[strings.ToUpper(methodName)] {
|
||||||
if len(c.methods) == 0 {
|
if len(c.methods) == 0 {
|
||||||
@ -577,18 +577,18 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
canskip := false
|
canSkip := false
|
||||||
for _, v := range l.wildcards {
|
for _, v := range l.wildcards {
|
||||||
if v == ":" {
|
if v == ":" {
|
||||||
canskip = true
|
canSkip = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if u, ok := params[v]; ok {
|
if u, ok := params[v]; ok {
|
||||||
delete(params, v)
|
delete(params, v)
|
||||||
url = strings.Replace(url, urlPlaceholder, u, 1)
|
url = strings.Replace(url, urlPlaceholder, u, 1)
|
||||||
} else {
|
} else {
|
||||||
if canskip {
|
if canSkip {
|
||||||
canskip = false
|
canSkip = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return false, ""
|
return false, ""
|
||||||
@ -597,27 +597,27 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin
|
|||||||
return true, url + toURL(params)
|
return true, url + toURL(params)
|
||||||
}
|
}
|
||||||
var i int
|
var i int
|
||||||
var startreg bool
|
var startReg bool
|
||||||
regurl := ""
|
regURL := ""
|
||||||
for _, v := range strings.Trim(l.regexps.String(), "^$") {
|
for _, v := range strings.Trim(l.regexps.String(), "^$") {
|
||||||
if v == '(' {
|
if v == '(' {
|
||||||
startreg = true
|
startReg = true
|
||||||
continue
|
continue
|
||||||
} else if v == ')' {
|
} else if v == ')' {
|
||||||
startreg = false
|
startReg = false
|
||||||
if v, ok := params[l.wildcards[i]]; ok {
|
if v, ok := params[l.wildcards[i]]; ok {
|
||||||
delete(params, l.wildcards[i])
|
delete(params, l.wildcards[i])
|
||||||
regurl = regurl + v
|
regURL = regURL + v
|
||||||
i++
|
i++
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else if !startreg {
|
} else if !startReg {
|
||||||
regurl = string(append([]rune(regurl), v))
|
regURL = string(append([]rune(regURL), v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if l.regexps.MatchString(regurl) {
|
if l.regexps.MatchString(regURL) {
|
||||||
ps := strings.Split(regurl, "/")
|
ps := strings.Split(regURL, "/")
|
||||||
for _, p := range ps {
|
for _, p := range ps {
|
||||||
url = strings.Replace(url, urlPlaceholder, p, 1)
|
url = strings.Replace(url, urlPlaceholder, p, 1)
|
||||||
}
|
}
|
||||||
@ -689,7 +689,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
// filter wrong http method
|
// filter wrong http method
|
||||||
if !HTTPMETHOD[r.Method] {
|
if !HTTPMETHOD[r.Method] {
|
||||||
http.Error(rw, "Method Not Allowed", 405)
|
exception("405", context)
|
||||||
goto Admin
|
goto Admin
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -773,12 +773,12 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
} else if routerInfo.routerType == routerTypeHandler {
|
} else if routerInfo.routerType == routerTypeHandler {
|
||||||
isRunnable = true
|
isRunnable = true
|
||||||
routerInfo.handler.ServeHTTP(rw, r)
|
routerInfo.handler.ServeHTTP(context.ResponseWriter, context.Request)
|
||||||
} else {
|
} else {
|
||||||
runRouter = routerInfo.controllerType
|
runRouter = routerInfo.controllerType
|
||||||
methodParams = routerInfo.methodParams
|
methodParams = routerInfo.methodParams
|
||||||
method := r.Method
|
method := r.Method
|
||||||
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPost {
|
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPut {
|
||||||
method = http.MethodPut
|
method = http.MethodPut
|
||||||
}
|
}
|
||||||
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodDelete {
|
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodDelete {
|
||||||
@ -843,6 +843,8 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
|||||||
execController.Patch()
|
execController.Patch()
|
||||||
case http.MethodOptions:
|
case http.MethodOptions:
|
||||||
execController.Options()
|
execController.Options()
|
||||||
|
case http.MethodTrace:
|
||||||
|
execController.Trace()
|
||||||
default:
|
default:
|
||||||
if !execController.HandlerFunc(runMethod) {
|
if !execController.HandlerFunc(runMethod) {
|
||||||
vc := reflect.ValueOf(execController)
|
vc := reflect.ValueOf(execController)
|
||||||
@ -888,49 +890,39 @@ Admin:
|
|||||||
statusCode = 200
|
statusCode = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
logAccess(context, &startTime, statusCode)
|
LogAccess(context, &startTime, statusCode)
|
||||||
|
|
||||||
|
timeDur := time.Since(startTime)
|
||||||
|
context.ResponseWriter.Elapsed = timeDur
|
||||||
if BConfig.Listen.EnableAdmin {
|
if BConfig.Listen.EnableAdmin {
|
||||||
timeDur := time.Since(startTime)
|
|
||||||
pattern := ""
|
pattern := ""
|
||||||
if routerInfo != nil {
|
if routerInfo != nil {
|
||||||
pattern = routerInfo.pattern
|
pattern = routerInfo.pattern
|
||||||
}
|
}
|
||||||
|
|
||||||
if FilterMonitorFunc(r.Method, r.URL.Path, timeDur, pattern, statusCode) {
|
if FilterMonitorFunc(r.Method, r.URL.Path, timeDur, pattern, statusCode) {
|
||||||
|
routerName := ""
|
||||||
if runRouter != nil {
|
if runRouter != nil {
|
||||||
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, runRouter.Name(), timeDur)
|
routerName = runRouter.Name()
|
||||||
} else {
|
|
||||||
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, "", timeDur)
|
|
||||||
}
|
}
|
||||||
|
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, routerName, timeDur)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs {
|
if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs {
|
||||||
var devInfo string
|
match := map[bool]string{true: "match", false: "nomatch"}
|
||||||
timeDur := time.Since(startTime)
|
devInfo := fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s",
|
||||||
iswin := (runtime.GOOS == "windows")
|
context.Input.IP(),
|
||||||
statusColor := logs.ColorByStatus(iswin, statusCode)
|
logs.ColorByStatus(statusCode), statusCode, logs.ResetColor(),
|
||||||
methodColor := logs.ColorByMethod(iswin, r.Method)
|
timeDur.String(),
|
||||||
resetColor := logs.ColorByMethod(iswin, "")
|
match[findRouter],
|
||||||
if findRouter {
|
logs.ColorByMethod(r.Method), r.Method, logs.ResetColor(),
|
||||||
if routerInfo != nil {
|
r.URL.Path)
|
||||||
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", context.Input.IP(), statusColor, statusCode,
|
if routerInfo != nil {
|
||||||
resetColor, timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path,
|
devInfo += fmt.Sprintf(" r:%s", routerInfo.pattern)
|
||||||
routerInfo.pattern)
|
|
||||||
} else {
|
|
||||||
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor,
|
|
||||||
timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor,
|
|
||||||
timeDur.String(), "nomatch", methodColor, r.Method, resetColor, r.URL.Path)
|
|
||||||
}
|
|
||||||
if iswin {
|
|
||||||
logs.W32Debug(devInfo)
|
|
||||||
} else {
|
|
||||||
logs.Debug(devInfo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logs.Debug(devInfo)
|
||||||
}
|
}
|
||||||
// Call WriteHeader if status code has been set changed
|
// Call WriteHeader if status code has been set changed
|
||||||
if context.Output.Status != 0 {
|
if context.Output.Status != 0 {
|
||||||
@ -979,7 +971,8 @@ func toURL(params map[string]string) string {
|
|||||||
return strings.TrimRight(u, "&")
|
return strings.TrimRight(u, "&")
|
||||||
}
|
}
|
||||||
|
|
||||||
func logAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) {
|
// LogAccess logging info HTTP Access
|
||||||
|
func LogAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) {
|
||||||
//Skip logging if AccessLogs config is false
|
//Skip logging if AccessLogs config is false
|
||||||
if !BConfig.Log.AccessLogs {
|
if !BConfig.Log.AccessLogs {
|
||||||
return
|
return
|
||||||
|
@ -71,10 +71,6 @@ func (tc *TestController) GetEmptyBody() {
|
|||||||
tc.Ctx.Output.Body(res)
|
tc.Ctx.Output.Body(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResStatus struct {
|
|
||||||
Code int
|
|
||||||
Msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
type JSONController struct {
|
type JSONController struct {
|
||||||
Controller
|
Controller
|
||||||
@ -475,7 +471,7 @@ func TestParamResetFilter(t *testing.T) {
|
|||||||
// a response header of `Splat`. The expectation here is that that Header
|
// a response header of `Splat`. The expectation here is that that Header
|
||||||
// value should match what the _request's_ router set, not the filter's.
|
// value should match what the _request's_ router set, not the filter's.
|
||||||
|
|
||||||
headers := rw.HeaderMap
|
headers := rw.Result().Header
|
||||||
if len(headers["Splat"]) != 1 {
|
if len(headers["Splat"]) != 1 {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"%s: There was an error in the test. Splat param not set in Header",
|
"%s: There was an error in the test. Splat param not set in Header",
|
||||||
@ -660,25 +656,16 @@ func beegoBeforeRouter1(ctx *context.Context) {
|
|||||||
ctx.WriteString("|BeforeRouter1")
|
ctx.WriteString("|BeforeRouter1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func beegoBeforeRouter2(ctx *context.Context) {
|
|
||||||
ctx.WriteString("|BeforeRouter2")
|
|
||||||
}
|
|
||||||
|
|
||||||
func beegoBeforeExec1(ctx *context.Context) {
|
func beegoBeforeExec1(ctx *context.Context) {
|
||||||
ctx.WriteString("|BeforeExec1")
|
ctx.WriteString("|BeforeExec1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func beegoBeforeExec2(ctx *context.Context) {
|
|
||||||
ctx.WriteString("|BeforeExec2")
|
|
||||||
}
|
|
||||||
|
|
||||||
func beegoAfterExec1(ctx *context.Context) {
|
func beegoAfterExec1(ctx *context.Context) {
|
||||||
ctx.WriteString("|AfterExec1")
|
ctx.WriteString("|AfterExec1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func beegoAfterExec2(ctx *context.Context) {
|
|
||||||
ctx.WriteString("|AfterExec2")
|
|
||||||
}
|
|
||||||
|
|
||||||
func beegoFinishRouter1(ctx *context.Context) {
|
func beegoFinishRouter1(ctx *context.Context) {
|
||||||
ctx.WriteString("|FinishRouter1")
|
ctx.WriteString("|FinishRouter1")
|
||||||
|
@ -133,7 +133,7 @@ func (lp *Provider) SessionRead(sid string) (session.Store, error) {
|
|||||||
// SessionExist check ledis session exist by sid
|
// SessionExist check ledis session exist by sid
|
||||||
func (lp *Provider) SessionExist(sid string) bool {
|
func (lp *Provider) SessionExist(sid string) bool {
|
||||||
count, _ := c.Exists([]byte(sid))
|
count, _ := c.Exists([]byte(sid))
|
||||||
return !(count == 0)
|
return count != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionRegenerate generate new sid for ledis session
|
// SessionRegenerate generate new sid for ledis session
|
||||||
|
@ -128,9 +128,12 @@ func (rp *MemProvider) SessionRead(sid string) (session.Store, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
item, err := client.Get(sid)
|
item, err := client.Get(sid)
|
||||||
if err != nil && err == memcache.ErrCacheMiss {
|
if err != nil {
|
||||||
rs := &SessionStore{sid: sid, values: make(map[interface{}]interface{}), maxlifetime: rp.maxlifetime}
|
if err == memcache.ErrCacheMiss {
|
||||||
return rs, nil
|
rs := &SessionStore{sid: sid, values: make(map[interface{}]interface{}), maxlifetime: rp.maxlifetime}
|
||||||
|
return rs, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
var kv map[interface{}]interface{}
|
var kv map[interface{}]interface{}
|
||||||
if len(item.Value) == 0 {
|
if len(item.Value) == 0 {
|
||||||
|
@ -170,7 +170,7 @@ func (mp *Provider) SessionExist(sid string) bool {
|
|||||||
row := c.QueryRow("select session_data from "+TableName+" where session_key=?", sid)
|
row := c.QueryRow("select session_data from "+TableName+" where session_key=?", sid)
|
||||||
var sessiondata []byte
|
var sessiondata []byte
|
||||||
err := row.Scan(&sessiondata)
|
err := row.Scan(&sessiondata)
|
||||||
return !(err == sql.ErrNoRows)
|
return err != sql.ErrNoRows
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionRegenerate generate new sid for mysql session
|
// SessionRegenerate generate new sid for mysql session
|
||||||
|
@ -184,7 +184,7 @@ func (mp *Provider) SessionExist(sid string) bool {
|
|||||||
row := c.QueryRow("select session_data from session where session_key=$1", sid)
|
row := c.QueryRow("select session_data from session where session_key=$1", sid)
|
||||||
var sessiondata []byte
|
var sessiondata []byte
|
||||||
err := row.Scan(&sessiondata)
|
err := row.Scan(&sessiondata)
|
||||||
return !(err == sql.ErrNoRows)
|
return err != sql.ErrNoRows
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionRegenerate generate new sid for postgresql session
|
// SessionRegenerate generate new sid for postgresql session
|
||||||
|
234
session/redis_sentinel/sess_redis_sentinel.go
Normal file
234
session/redis_sentinel/sess_redis_sentinel.go
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// 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 redis for session provider
|
||||||
|
//
|
||||||
|
// depend on github.com/go-redis/redis
|
||||||
|
//
|
||||||
|
// go install github.com/go-redis/redis
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// import(
|
||||||
|
// _ "github.com/astaxie/beego/session/redis_sentinel"
|
||||||
|
// "github.com/astaxie/beego/session"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func init() {
|
||||||
|
// globalSessions, _ = session.NewManager("redis_sentinel", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:26379;127.0.0.2:26379"}``)
|
||||||
|
// go globalSessions.GC()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// more detail about params: please check the notes on the function SessionInit in this package
|
||||||
|
package redis_sentinel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/session"
|
||||||
|
"github.com/go-redis/redis"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var redispder = &Provider{}
|
||||||
|
|
||||||
|
// DefaultPoolSize redis_sentinel default pool size
|
||||||
|
var DefaultPoolSize = 100
|
||||||
|
|
||||||
|
// SessionStore redis_sentinel session store
|
||||||
|
type SessionStore struct {
|
||||||
|
p *redis.Client
|
||||||
|
sid string
|
||||||
|
lock sync.RWMutex
|
||||||
|
values map[interface{}]interface{}
|
||||||
|
maxlifetime int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set value in redis_sentinel session
|
||||||
|
func (rs *SessionStore) Set(key, value interface{}) error {
|
||||||
|
rs.lock.Lock()
|
||||||
|
defer rs.lock.Unlock()
|
||||||
|
rs.values[key] = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get value in redis_sentinel session
|
||||||
|
func (rs *SessionStore) Get(key interface{}) interface{} {
|
||||||
|
rs.lock.RLock()
|
||||||
|
defer rs.lock.RUnlock()
|
||||||
|
if v, ok := rs.values[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete value in redis_sentinel session
|
||||||
|
func (rs *SessionStore) Delete(key interface{}) error {
|
||||||
|
rs.lock.Lock()
|
||||||
|
defer rs.lock.Unlock()
|
||||||
|
delete(rs.values, key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush clear all values in redis_sentinel session
|
||||||
|
func (rs *SessionStore) Flush() error {
|
||||||
|
rs.lock.Lock()
|
||||||
|
defer rs.lock.Unlock()
|
||||||
|
rs.values = make(map[interface{}]interface{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionID get redis_sentinel session id
|
||||||
|
func (rs *SessionStore) SessionID() string {
|
||||||
|
return rs.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionRelease save session values to redis_sentinel
|
||||||
|
func (rs *SessionStore) SessionRelease(w http.ResponseWriter) {
|
||||||
|
b, err := session.EncodeGob(rs.values)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c := rs.p
|
||||||
|
c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime)*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider redis_sentinel session provider
|
||||||
|
type Provider struct {
|
||||||
|
maxlifetime int64
|
||||||
|
savePath string
|
||||||
|
poolsize int
|
||||||
|
password string
|
||||||
|
dbNum int
|
||||||
|
poollist *redis.Client
|
||||||
|
masterName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionInit init redis_sentinel session
|
||||||
|
// savepath like redis sentinel addr,pool size,password,dbnum,masterName
|
||||||
|
// e.g. 127.0.0.1:26379;127.0.0.2:26379,100,1qaz2wsx,0,mymaster
|
||||||
|
func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
|
||||||
|
rp.maxlifetime = maxlifetime
|
||||||
|
configs := strings.Split(savePath, ",")
|
||||||
|
if len(configs) > 0 {
|
||||||
|
rp.savePath = configs[0]
|
||||||
|
}
|
||||||
|
if len(configs) > 1 {
|
||||||
|
poolsize, err := strconv.Atoi(configs[1])
|
||||||
|
if err != nil || poolsize < 0 {
|
||||||
|
rp.poolsize = DefaultPoolSize
|
||||||
|
} else {
|
||||||
|
rp.poolsize = poolsize
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rp.poolsize = DefaultPoolSize
|
||||||
|
}
|
||||||
|
if len(configs) > 2 {
|
||||||
|
rp.password = configs[2]
|
||||||
|
}
|
||||||
|
if len(configs) > 3 {
|
||||||
|
dbnum, err := strconv.Atoi(configs[3])
|
||||||
|
if err != nil || dbnum < 0 {
|
||||||
|
rp.dbNum = 0
|
||||||
|
} else {
|
||||||
|
rp.dbNum = dbnum
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rp.dbNum = 0
|
||||||
|
}
|
||||||
|
if len(configs) > 4 {
|
||||||
|
if configs[4] != "" {
|
||||||
|
rp.masterName = configs[4]
|
||||||
|
} else {
|
||||||
|
rp.masterName = "mymaster"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rp.masterName = "mymaster"
|
||||||
|
}
|
||||||
|
|
||||||
|
rp.poollist = redis.NewFailoverClient(&redis.FailoverOptions{
|
||||||
|
SentinelAddrs: strings.Split(rp.savePath, ";"),
|
||||||
|
Password: rp.password,
|
||||||
|
PoolSize: rp.poolsize,
|
||||||
|
DB: rp.dbNum,
|
||||||
|
MasterName: rp.masterName,
|
||||||
|
})
|
||||||
|
|
||||||
|
return rp.poollist.Ping().Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionRead read redis_sentinel session by sid
|
||||||
|
func (rp *Provider) SessionRead(sid string) (session.Store, error) {
|
||||||
|
var kv map[interface{}]interface{}
|
||||||
|
kvs, err := rp.poollist.Get(sid).Result()
|
||||||
|
if err != nil && err != redis.Nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(kvs) == 0 {
|
||||||
|
kv = make(map[interface{}]interface{})
|
||||||
|
} else {
|
||||||
|
if kv, err = session.DecodeGob([]byte(kvs)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := &SessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime}
|
||||||
|
return rs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionExist check redis_sentinel session exist by sid
|
||||||
|
func (rp *Provider) SessionExist(sid string) bool {
|
||||||
|
c := rp.poollist
|
||||||
|
if existed, err := c.Exists(sid).Result(); err != nil || existed == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionRegenerate generate new sid for redis_sentinel session
|
||||||
|
func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) {
|
||||||
|
c := rp.poollist
|
||||||
|
|
||||||
|
if existed, err := c.Exists(oldsid).Result(); err != nil || existed == 0 {
|
||||||
|
// oldsid doesn't exists, set the new sid directly
|
||||||
|
// ignore error here, since if it return error
|
||||||
|
// the existed value will be 0
|
||||||
|
c.Set(sid, "", time.Duration(rp.maxlifetime)*time.Second)
|
||||||
|
} else {
|
||||||
|
c.Rename(oldsid, sid)
|
||||||
|
c.Expire(sid, time.Duration(rp.maxlifetime)*time.Second)
|
||||||
|
}
|
||||||
|
return rp.SessionRead(sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionDestroy delete redis session by id
|
||||||
|
func (rp *Provider) SessionDestroy(sid string) error {
|
||||||
|
c := rp.poollist
|
||||||
|
c.Del(sid)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionGC Impelment method, no used.
|
||||||
|
func (rp *Provider) SessionGC() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionAll return all activeSession
|
||||||
|
func (rp *Provider) SessionAll() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
session.Register("redis_sentinel", redispder)
|
||||||
|
}
|
90
session/redis_sentinel/sess_redis_sentinel_test.go
Normal file
90
session/redis_sentinel/sess_redis_sentinel_test.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package redis_sentinel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRedisSentinel(t *testing.T) {
|
||||||
|
sessionConfig := &session.ManagerConfig{
|
||||||
|
CookieName: "gosessionid",
|
||||||
|
EnableSetCookie: true,
|
||||||
|
Gclifetime: 3600,
|
||||||
|
Maxlifetime: 3600,
|
||||||
|
Secure: false,
|
||||||
|
CookieLifeTime: 3600,
|
||||||
|
ProviderConfig: "127.0.0.1:6379,100,,0,master",
|
||||||
|
}
|
||||||
|
globalSessions, e := session.NewManager("redis_sentinel", sessionConfig)
|
||||||
|
if e != nil {
|
||||||
|
t.Log(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//todo test if e==nil
|
||||||
|
go globalSessions.GC()
|
||||||
|
|
||||||
|
r, _ := http.NewRequest("GET", "/", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
sess, err := globalSessions.SessionStart(w, r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("session start failed:", err)
|
||||||
|
}
|
||||||
|
defer sess.SessionRelease(w)
|
||||||
|
|
||||||
|
// SET AND GET
|
||||||
|
err = sess.Set("username", "astaxie")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("set username failed:", err)
|
||||||
|
}
|
||||||
|
username := sess.Get("username")
|
||||||
|
if username != "astaxie" {
|
||||||
|
t.Fatal("get username failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
err = sess.Delete("username")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("delete username failed:", err)
|
||||||
|
}
|
||||||
|
username = sess.Get("username")
|
||||||
|
if username != nil {
|
||||||
|
t.Fatal("delete username failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FLUSH
|
||||||
|
err = sess.Set("username", "astaxie")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("set failed:", err)
|
||||||
|
}
|
||||||
|
err = sess.Set("password", "1qaz2wsx")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("set failed:", err)
|
||||||
|
}
|
||||||
|
username = sess.Get("username")
|
||||||
|
if username != "astaxie" {
|
||||||
|
t.Fatal("get username failed")
|
||||||
|
}
|
||||||
|
password := sess.Get("password")
|
||||||
|
if password != "1qaz2wsx" {
|
||||||
|
t.Fatal("get password failed")
|
||||||
|
}
|
||||||
|
err = sess.Flush()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("flush failed:", err)
|
||||||
|
}
|
||||||
|
username = sess.Get("username")
|
||||||
|
if username != nil {
|
||||||
|
t.Fatal("flush failed")
|
||||||
|
}
|
||||||
|
password = sess.Get("password")
|
||||||
|
if password != nil {
|
||||||
|
t.Fatal("flush failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
sess.SessionRelease(w)
|
||||||
|
|
||||||
|
}
|
@ -19,6 +19,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"errors"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -131,6 +132,9 @@ func (fp *FileProvider) SessionRead(sid string) (Store, error) {
|
|||||||
if strings.ContainsAny(sid, "./") {
|
if strings.ContainsAny(sid, "./") {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
if len(sid) < 2 {
|
||||||
|
return nil, errors.New("length of the sid is less than 2")
|
||||||
|
}
|
||||||
filepder.lock.Lock()
|
filepder.lock.Lock()
|
||||||
defer filepder.lock.Unlock()
|
defer filepder.lock.Unlock()
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha1"
|
"crypto/sha256"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
@ -129,7 +129,7 @@ func encodeCookie(block cipher.Block, hashKey, name string, value map[interface{
|
|||||||
b = encode(b)
|
b = encode(b)
|
||||||
// 3. Create MAC for "name|date|value". Extra pipe to be used later.
|
// 3. Create MAC for "name|date|value". Extra pipe to be used later.
|
||||||
b = []byte(fmt.Sprintf("%s|%d|%s|", name, time.Now().UTC().Unix(), b))
|
b = []byte(fmt.Sprintf("%s|%d|%s|", name, time.Now().UTC().Unix(), b))
|
||||||
h := hmac.New(sha1.New, []byte(hashKey))
|
h := hmac.New(sha256.New, []byte(hashKey))
|
||||||
h.Write(b)
|
h.Write(b)
|
||||||
sig := h.Sum(nil)
|
sig := h.Sum(nil)
|
||||||
// Append mac, remove name.
|
// Append mac, remove name.
|
||||||
@ -153,7 +153,7 @@ func decodeCookie(block cipher.Block, hashKey, name, value string, gcmaxlifetime
|
|||||||
}
|
}
|
||||||
|
|
||||||
b = append([]byte(name+"|"), b[:len(b)-len(parts[2])]...)
|
b = append([]byte(name+"|"), b[:len(b)-len(parts[2])]...)
|
||||||
h := hmac.New(sha1.New, []byte(hashKey))
|
h := hmac.New(sha256.New, []byte(hashKey))
|
||||||
h.Write(b)
|
h.Write(b)
|
||||||
sig := h.Sum(nil)
|
sig := h.Sum(nil)
|
||||||
if len(sig) != len(parts[2]) || subtle.ConstantTimeCompare(sig, parts[2]) != 1 {
|
if len(sig) != len(parts[2]) || subtle.ConstantTimeCompare(sig, parts[2]) != 1 {
|
||||||
|
@ -81,6 +81,15 @@ func Register(name string, provide Provider) {
|
|||||||
provides[name] = provide
|
provides[name] = provide
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//GetProvider
|
||||||
|
func GetProvider(name string) (Provider, error) {
|
||||||
|
provider, ok := provides[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", name)
|
||||||
|
}
|
||||||
|
return provider, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ManagerConfig define the session config
|
// ManagerConfig define the session config
|
||||||
type ManagerConfig struct {
|
type ManagerConfig struct {
|
||||||
CookieName string `json:"cookieName"`
|
CookieName string `json:"cookieName"`
|
||||||
@ -261,7 +270,8 @@ func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
|
|||||||
Path: "/",
|
Path: "/",
|
||||||
HttpOnly: !manager.config.DisableHTTPOnly,
|
HttpOnly: !manager.config.DisableHTTPOnly,
|
||||||
Expires: expiration,
|
Expires: expiration,
|
||||||
MaxAge: -1}
|
MaxAge: -1,
|
||||||
|
Domain: manager.config.Domain}
|
||||||
|
|
||||||
http.SetCookie(w, cookie)
|
http.SetCookie(w, cookie)
|
||||||
}
|
}
|
||||||
|
10
template.go
10
template.go
@ -38,7 +38,7 @@ var (
|
|||||||
beeViewPathTemplates = make(map[string]map[string]*template.Template)
|
beeViewPathTemplates = make(map[string]map[string]*template.Template)
|
||||||
templatesLock sync.RWMutex
|
templatesLock sync.RWMutex
|
||||||
// beeTemplateExt stores the template extension which will build
|
// beeTemplateExt stores the template extension which will build
|
||||||
beeTemplateExt = []string{"tpl", "html"}
|
beeTemplateExt = []string{"tpl", "html", "gohtml"}
|
||||||
// beeTemplatePreprocessors stores associations of extension -> preprocessor handler
|
// beeTemplatePreprocessors stores associations of extension -> preprocessor handler
|
||||||
beeTemplateEngines = map[string]templatePreProcessor{}
|
beeTemplateEngines = map[string]templatePreProcessor{}
|
||||||
beeTemplateFS = defaultFSFunc
|
beeTemplateFS = defaultFSFunc
|
||||||
@ -186,13 +186,13 @@ func BuildTemplate(dir string, files ...string) error {
|
|||||||
var err error
|
var err error
|
||||||
fs := beeTemplateFS()
|
fs := beeTemplateFS()
|
||||||
f, err := fs.Open(dir)
|
f, err := fs.Open(dir)
|
||||||
defer f.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New("dir open err")
|
return errors.New("dir open err")
|
||||||
}
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
beeTemplates, ok := beeViewPathTemplates[dir]
|
beeTemplates, ok := beeViewPathTemplates[dir]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -240,7 +240,7 @@ func getTplDeep(root string, fs http.FileSystem, file string, parent string, t *
|
|||||||
var fileAbsPath string
|
var fileAbsPath string
|
||||||
var rParent string
|
var rParent string
|
||||||
var err error
|
var err error
|
||||||
if filepath.HasPrefix(file, "../") {
|
if strings.HasPrefix(file, "../") {
|
||||||
rParent = filepath.Join(filepath.Dir(parent), file)
|
rParent = filepath.Join(filepath.Dir(parent), file)
|
||||||
fileAbsPath = filepath.Join(root, filepath.Dir(parent), file)
|
fileAbsPath = filepath.Join(root, filepath.Dir(parent), file)
|
||||||
} else {
|
} else {
|
||||||
@ -248,10 +248,10 @@ func getTplDeep(root string, fs http.FileSystem, file string, parent string, t *
|
|||||||
fileAbsPath = filepath.Join(root, file)
|
fileAbsPath = filepath.Join(root, file)
|
||||||
}
|
}
|
||||||
f, err := fs.Open(fileAbsPath)
|
f, err := fs.Open(fileAbsPath)
|
||||||
defer f.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("can't find template file:" + file)
|
panic("can't find template file:" + file)
|
||||||
}
|
}
|
||||||
|
defer f.Close()
|
||||||
data, err := ioutil.ReadAll(f)
|
data, err := ioutil.ReadAll(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, [][]string{}, err
|
return nil, [][]string{}, err
|
||||||
@ -361,6 +361,8 @@ type templateFSFunc func() http.FileSystem
|
|||||||
func defaultFSFunc() http.FileSystem {
|
func defaultFSFunc() http.FileSystem {
|
||||||
return FileSystem{}
|
return FileSystem{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTemplateFSFunc set default filesystem function
|
||||||
func SetTemplateFSFunc(fnt templateFSFunc) {
|
func SetTemplateFSFunc(fnt templateFSFunc) {
|
||||||
beeTemplateFS = fnt
|
beeTemplateFS = fnt
|
||||||
}
|
}
|
||||||
|
@ -55,21 +55,21 @@ func Substr(s string, start, length int) string {
|
|||||||
// HTML2str returns escaping text convert from html.
|
// HTML2str returns escaping text convert from html.
|
||||||
func HTML2str(html string) string {
|
func HTML2str(html string) string {
|
||||||
|
|
||||||
re, _ := regexp.Compile(`\<[\S\s]+?\>`)
|
re := regexp.MustCompile(`\<[\S\s]+?\>`)
|
||||||
html = re.ReplaceAllStringFunc(html, strings.ToLower)
|
html = re.ReplaceAllStringFunc(html, strings.ToLower)
|
||||||
|
|
||||||
//remove STYLE
|
//remove STYLE
|
||||||
re, _ = regexp.Compile(`\<style[\S\s]+?\</style\>`)
|
re = regexp.MustCompile(`\<style[\S\s]+?\</style\>`)
|
||||||
html = re.ReplaceAllString(html, "")
|
html = re.ReplaceAllString(html, "")
|
||||||
|
|
||||||
//remove SCRIPT
|
//remove SCRIPT
|
||||||
re, _ = regexp.Compile(`\<script[\S\s]+?\</script\>`)
|
re = regexp.MustCompile(`\<script[\S\s]+?\</script\>`)
|
||||||
html = re.ReplaceAllString(html, "")
|
html = re.ReplaceAllString(html, "")
|
||||||
|
|
||||||
re, _ = regexp.Compile(`\<[\S\s]+?\>`)
|
re = regexp.MustCompile(`\<[\S\s]+?\>`)
|
||||||
html = re.ReplaceAllString(html, "\n")
|
html = re.ReplaceAllString(html, "\n")
|
||||||
|
|
||||||
re, _ = regexp.Compile(`\s{2,}`)
|
re = regexp.MustCompile(`\s{2,}`)
|
||||||
html = re.ReplaceAllString(html, "\n")
|
html = re.ReplaceAllString(html, "\n")
|
||||||
|
|
||||||
return strings.TrimSpace(html)
|
return strings.TrimSpace(html)
|
||||||
@ -85,24 +85,24 @@ func DateFormat(t time.Time, layout string) (datestring string) {
|
|||||||
var datePatterns = []string{
|
var datePatterns = []string{
|
||||||
// year
|
// year
|
||||||
"Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003
|
"Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003
|
||||||
"y", "06", //A two digit representation of a year Examples: 99 or 03
|
"y", "06", //A two digit representation of a year Examples: 99 or 03
|
||||||
|
|
||||||
// month
|
// month
|
||||||
"m", "01", // Numeric representation of a month, with leading zeros 01 through 12
|
"m", "01", // Numeric representation of a month, with leading zeros 01 through 12
|
||||||
"n", "1", // Numeric representation of a month, without leading zeros 1 through 12
|
"n", "1", // Numeric representation of a month, without leading zeros 1 through 12
|
||||||
"M", "Jan", // A short textual representation of a month, three letters Jan through Dec
|
"M", "Jan", // A short textual representation of a month, three letters Jan through Dec
|
||||||
"F", "January", // A full textual representation of a month, such as January or March January through December
|
"F", "January", // A full textual representation of a month, such as January or March January through December
|
||||||
|
|
||||||
// day
|
// day
|
||||||
"d", "02", // Day of the month, 2 digits with leading zeros 01 to 31
|
"d", "02", // Day of the month, 2 digits with leading zeros 01 to 31
|
||||||
"j", "2", // Day of the month without leading zeros 1 to 31
|
"j", "2", // Day of the month without leading zeros 1 to 31
|
||||||
|
|
||||||
// week
|
// week
|
||||||
"D", "Mon", // A textual representation of a day, three letters Mon through Sun
|
"D", "Mon", // A textual representation of a day, three letters Mon through Sun
|
||||||
"l", "Monday", // A full textual representation of the day of the week Sunday through Saturday
|
"l", "Monday", // A full textual representation of the day of the week Sunday through Saturday
|
||||||
|
|
||||||
// time
|
// time
|
||||||
"g", "3", // 12-hour format of an hour without leading zeros 1 through 12
|
"g", "3", // 12-hour format of an hour without leading zeros 1 through 12
|
||||||
"G", "15", // 24-hour format of an hour without leading zeros 0 through 23
|
"G", "15", // 24-hour format of an hour without leading zeros 0 through 23
|
||||||
"h", "03", // 12-hour format of an hour with leading zeros 01 through 12
|
"h", "03", // 12-hour format of an hour with leading zeros 01 through 12
|
||||||
"H", "15", // 24-hour format of an hour with leading zeros 00 through 23
|
"H", "15", // 24-hour format of an hour with leading zeros 00 through 23
|
||||||
@ -172,7 +172,7 @@ func GetConfig(returnType, key string, defaultVal interface{}) (value interface{
|
|||||||
case "DIY":
|
case "DIY":
|
||||||
value, err = AppConfig.DIY(key)
|
value, err = AppConfig.DIY(key)
|
||||||
default:
|
default:
|
||||||
err = errors.New("Config keys must be of type String, Bool, Int, Int64, Float, or DIY")
|
err = errors.New("config keys must be of type String, Bool, Int, Int64, Float, or DIY")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -297,9 +297,21 @@ func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) e
|
|||||||
tag = tags[0]
|
tag = tags[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
value := form.Get(tag)
|
formValues := form[tag]
|
||||||
if len(value) == 0 {
|
var value string
|
||||||
continue
|
if len(formValues) == 0 {
|
||||||
|
defaultValue := fieldT.Tag.Get("default")
|
||||||
|
if defaultValue != "" {
|
||||||
|
value = defaultValue
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(formValues) == 1 {
|
||||||
|
value = formValues[0]
|
||||||
|
if value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch fieldT.Type.Kind() {
|
switch fieldT.Type.Kind() {
|
||||||
@ -349,6 +361,8 @@ func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) e
|
|||||||
if len(value) >= 25 {
|
if len(value) >= 25 {
|
||||||
value = value[:25]
|
value = value[:25]
|
||||||
t, err = time.ParseInLocation(time.RFC3339, value, time.Local)
|
t, err = time.ParseInLocation(time.RFC3339, value, time.Local)
|
||||||
|
} else if strings.HasSuffix(strings.ToUpper(value), "Z") {
|
||||||
|
t, err = time.ParseInLocation(time.RFC3339, value, time.Local)
|
||||||
} else if len(value) >= 19 {
|
} else if len(value) >= 19 {
|
||||||
if strings.Contains(value, "T") {
|
if strings.Contains(value, "T") {
|
||||||
value = value[:19]
|
value = value[:19]
|
||||||
|
@ -111,7 +111,7 @@ func TestHtmlunquote(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseForm(t *testing.T) {
|
func TestParseForm(t *testing.T) {
|
||||||
type ExtendInfo struct {
|
type ExtendInfo struct {
|
||||||
Hobby string `form:"hobby"`
|
Hobby []string `form:"hobby"`
|
||||||
Memo string
|
Memo string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ func TestParseForm(t *testing.T) {
|
|||||||
"date": []string{"2014-11-12"},
|
"date": []string{"2014-11-12"},
|
||||||
"organization": []string{"beego"},
|
"organization": []string{"beego"},
|
||||||
"title": []string{"CXO"},
|
"title": []string{"CXO"},
|
||||||
"hobby": []string{"Basketball"},
|
"hobby": []string{"", "Basketball", "Football"},
|
||||||
"memo": []string{"nothing"},
|
"memo": []string{"nothing"},
|
||||||
}
|
}
|
||||||
if err := ParseForm(form, u); err == nil {
|
if err := ParseForm(form, u); err == nil {
|
||||||
@ -186,8 +186,14 @@ func TestParseForm(t *testing.T) {
|
|||||||
if u.Title != "CXO" {
|
if u.Title != "CXO" {
|
||||||
t.Errorf("Title should equal `CXO`, but got `%v`", u.Title)
|
t.Errorf("Title should equal `CXO`, but got `%v`", u.Title)
|
||||||
}
|
}
|
||||||
if u.Hobby != "Basketball" {
|
if u.Hobby[0] != "" {
|
||||||
t.Errorf("Hobby should equal `Basketball`, but got `%v`", u.Hobby)
|
t.Errorf("Hobby should equal ``, but got `%v`", u.Hobby[0])
|
||||||
|
}
|
||||||
|
if u.Hobby[1] != "Basketball" {
|
||||||
|
t.Errorf("Hobby should equal `Basketball`, but got `%v`", u.Hobby[1])
|
||||||
|
}
|
||||||
|
if u.Hobby[2] != "Football" {
|
||||||
|
t.Errorf("Hobby should equal `Football`, but got `%v`", u.Hobby[2])
|
||||||
}
|
}
|
||||||
if len(u.Memo) != 0 {
|
if len(u.Memo) != 0 {
|
||||||
t.Errorf("Memo's length should equal 0 but got %v", len(u.Memo))
|
t.Errorf("Memo's length should equal 0 but got %v", len(u.Memo))
|
||||||
@ -197,7 +203,6 @@ func TestParseForm(t *testing.T) {
|
|||||||
func TestRenderForm(t *testing.T) {
|
func TestRenderForm(t *testing.T) {
|
||||||
type user struct {
|
type user struct {
|
||||||
ID int `form:"-"`
|
ID int `form:"-"`
|
||||||
tag string `form:"tag"`
|
|
||||||
Name interface{} `form:"username"`
|
Name interface{} `form:"username"`
|
||||||
Age int `form:"age,text,年龄:"`
|
Age int `form:"age,text,年龄:"`
|
||||||
Sex string
|
Sex string
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ type bounds struct {
|
|||||||
// The bounds for each field.
|
// The bounds for each field.
|
||||||
var (
|
var (
|
||||||
AdminTaskList map[string]Tasker
|
AdminTaskList map[string]Tasker
|
||||||
|
taskLock sync.Mutex
|
||||||
stop chan bool
|
stop chan bool
|
||||||
changed chan bool
|
changed chan bool
|
||||||
isstart bool
|
isstart bool
|
||||||
@ -389,6 +391,8 @@ func dayMatches(s *Schedule, t time.Time) bool {
|
|||||||
|
|
||||||
// StartTask start all tasks
|
// StartTask start all tasks
|
||||||
func StartTask() {
|
func StartTask() {
|
||||||
|
taskLock.Lock()
|
||||||
|
defer taskLock.Unlock()
|
||||||
if isstart {
|
if isstart {
|
||||||
//If already started, no need to start another goroutine.
|
//If already started, no need to start another goroutine.
|
||||||
return
|
return
|
||||||
@ -440,6 +444,8 @@ func run() {
|
|||||||
|
|
||||||
// StopTask stop all tasks
|
// StopTask stop all tasks
|
||||||
func StopTask() {
|
func StopTask() {
|
||||||
|
taskLock.Lock()
|
||||||
|
defer taskLock.Unlock()
|
||||||
if isstart {
|
if isstart {
|
||||||
isstart = false
|
isstart = false
|
||||||
stop <- true
|
stop <- true
|
||||||
@ -449,6 +455,8 @@ func StopTask() {
|
|||||||
|
|
||||||
// AddTask add task with name
|
// AddTask add task with name
|
||||||
func AddTask(taskname string, t Tasker) {
|
func AddTask(taskname string, t Tasker) {
|
||||||
|
taskLock.Lock()
|
||||||
|
defer taskLock.Unlock()
|
||||||
t.SetNext(time.Now().Local())
|
t.SetNext(time.Now().Local())
|
||||||
AdminTaskList[taskname] = t
|
AdminTaskList[taskname] = t
|
||||||
if isstart {
|
if isstart {
|
||||||
@ -458,6 +466,8 @@ func AddTask(taskname string, t Tasker) {
|
|||||||
|
|
||||||
// DeleteTask delete task with name
|
// DeleteTask delete task with name
|
||||||
func DeleteTask(taskname string) {
|
func DeleteTask(taskname string) {
|
||||||
|
taskLock.Lock()
|
||||||
|
defer taskLock.Unlock()
|
||||||
delete(AdminTaskList, taskname)
|
delete(AdminTaskList, taskname)
|
||||||
if isstart {
|
if isstart {
|
||||||
changed <- true
|
changed <- true
|
||||||
|
@ -162,7 +162,7 @@ func (e *Email) Bytes() ([]byte, error) {
|
|||||||
|
|
||||||
// AttachFile Add attach file to the send mail
|
// AttachFile Add attach file to the send mail
|
||||||
func (e *Email) AttachFile(args ...string) (a *Attachment, err error) {
|
func (e *Email) AttachFile(args ...string) (a *Attachment, err error) {
|
||||||
if len(args) < 1 && len(args) > 2 {
|
if len(args) < 1 || len(args) > 2 { // change && to ||
|
||||||
err = errors.New("Must specify a file name and number of parameters can not exceed at least two")
|
err = errors.New("Must specify a file name and number of parameters can not exceed at least two")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -175,6 +175,7 @@ func (e *Email) AttachFile(args ...string) (a *Attachment, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer f.Close()
|
||||||
ct := mime.TypeByExtension(filepath.Ext(filename))
|
ct := mime.TypeByExtension(filepath.Ext(filename))
|
||||||
basename := path.Base(filename)
|
basename := path.Base(filename)
|
||||||
return e.Attach(f, basename, ct, id)
|
return e.Attach(f, basename, ct, id)
|
||||||
@ -183,7 +184,7 @@ func (e *Email) AttachFile(args ...string) (a *Attachment, err error) {
|
|||||||
// Attach is used to attach content from an io.Reader to the email.
|
// Attach is used to attach content from an io.Reader to the email.
|
||||||
// Parameters include an io.Reader, the desired filename for the attachment, and the Content-Type.
|
// Parameters include an io.Reader, the desired filename for the attachment, and the Content-Type.
|
||||||
func (e *Email) Attach(r io.Reader, filename string, args ...string) (a *Attachment, err error) {
|
func (e *Email) Attach(r io.Reader, filename string, args ...string) (a *Attachment, err error) {
|
||||||
if len(args) < 1 && len(args) > 2 {
|
if len(args) < 1 || len(args) > 2 { // change && to ||
|
||||||
err = errors.New("Must specify the file type and number of parameters can not exceed at least two")
|
err = errors.New("Must specify the file type and number of parameters can not exceed at least two")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,78 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetGOPATHs returns all paths in GOPATH variable.
|
// GetGOPATHs returns all paths in GOPATH variable.
|
||||||
func GetGOPATHs() []string {
|
func GetGOPATHs() []string {
|
||||||
gopath := os.Getenv("GOPATH")
|
gopath := os.Getenv("GOPATH")
|
||||||
if gopath == "" && strings.Compare(runtime.Version(), "go1.8") >= 0 {
|
if gopath == "" && compareGoVersion(runtime.Version(), "go1.8") >= 0 {
|
||||||
gopath = defaultGOPATH()
|
gopath = defaultGOPATH()
|
||||||
}
|
}
|
||||||
return filepath.SplitList(gopath)
|
return filepath.SplitList(gopath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func compareGoVersion(a, b string) int {
|
||||||
|
reg := regexp.MustCompile("^\\d*")
|
||||||
|
|
||||||
|
a = strings.TrimPrefix(a, "go")
|
||||||
|
b = strings.TrimPrefix(b, "go")
|
||||||
|
|
||||||
|
versionsA := strings.Split(a, ".")
|
||||||
|
versionsB := strings.Split(b, ".")
|
||||||
|
|
||||||
|
for i := 0; i < len(versionsA) && i < len(versionsB); i++ {
|
||||||
|
versionA := versionsA[i]
|
||||||
|
versionB := versionsB[i]
|
||||||
|
|
||||||
|
vA, err := strconv.Atoi(versionA)
|
||||||
|
if err != nil {
|
||||||
|
str := reg.FindString(versionA)
|
||||||
|
if str != "" {
|
||||||
|
vA, _ = strconv.Atoi(str)
|
||||||
|
} else {
|
||||||
|
vA = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vB, err := strconv.Atoi(versionB)
|
||||||
|
if err != nil {
|
||||||
|
str := reg.FindString(versionB)
|
||||||
|
if str != "" {
|
||||||
|
vB, _ = strconv.Atoi(str)
|
||||||
|
} else {
|
||||||
|
vB = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vA > vB {
|
||||||
|
// vA = 12, vB = 8
|
||||||
|
return 1
|
||||||
|
} else if vA < vB {
|
||||||
|
// vA = 6, vB = 8
|
||||||
|
return -1
|
||||||
|
} else if vA == -1 {
|
||||||
|
// vA = rc1, vB = rc3
|
||||||
|
return strings.Compare(versionA, versionB)
|
||||||
|
}
|
||||||
|
|
||||||
|
// vA = vB = 8
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(versionsA) > len(versionsB) {
|
||||||
|
return 1
|
||||||
|
} else if len(versionsA) == len(versionsB) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
func defaultGOPATH() string {
|
func defaultGOPATH() string {
|
||||||
env := "HOME"
|
env := "HOME"
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
|
36
utils/utils_test.go
Normal file
36
utils/utils_test.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCompareGoVersion(t *testing.T) {
|
||||||
|
targetVersion := "go1.8"
|
||||||
|
if compareGoVersion("go1.12.4", targetVersion) != 1 {
|
||||||
|
t.Error("should be 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if compareGoVersion("go1.8.7", targetVersion) != 1 {
|
||||||
|
t.Error("should be 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if compareGoVersion("go1.8", targetVersion) != 0 {
|
||||||
|
t.Error("should be 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if compareGoVersion("go1.7.6", targetVersion) != -1 {
|
||||||
|
t.Error("should be -1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if compareGoVersion("go1.12.1rc1", targetVersion) != 1 {
|
||||||
|
t.Error("should be 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if compareGoVersion("go1.8rc1", targetVersion) != 0 {
|
||||||
|
t.Error("should be 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if compareGoVersion("go1.7rc1", targetVersion) != -1 {
|
||||||
|
t.Error("should be -1")
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,8 @@ const (
|
|||||||
// ValidTag struct tag
|
// ValidTag struct tag
|
||||||
ValidTag = "valid"
|
ValidTag = "valid"
|
||||||
|
|
||||||
|
LabelTag = "label"
|
||||||
|
|
||||||
wordsize = 32 << (^uint(0) >> 32 & 1)
|
wordsize = 32 << (^uint(0) >> 32 & 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -124,6 +126,7 @@ func isStructPtr(t reflect.Type) bool {
|
|||||||
|
|
||||||
func getValidFuncs(f reflect.StructField) (vfs []ValidFunc, err error) {
|
func getValidFuncs(f reflect.StructField) (vfs []ValidFunc, err error) {
|
||||||
tag := f.Tag.Get(ValidTag)
|
tag := f.Tag.Get(ValidTag)
|
||||||
|
label := f.Tag.Get(LabelTag)
|
||||||
if len(tag) == 0 {
|
if len(tag) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -136,7 +139,7 @@ func getValidFuncs(f reflect.StructField) (vfs []ValidFunc, err error) {
|
|||||||
if len(vfunc) == 0 {
|
if len(vfunc) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
vf, err = parseFunc(vfunc, f.Name)
|
vf, err = parseFunc(vfunc, f.Name, label)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -168,7 +171,7 @@ func getRegFuncs(tag, key string) (vfs []ValidFunc, str string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFunc(vfunc, key string) (v ValidFunc, err error) {
|
func parseFunc(vfunc, key string, label string) (v ValidFunc, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
err = fmt.Errorf("%v", r)
|
err = fmt.Errorf("%v", r)
|
||||||
@ -188,7 +191,7 @@ func parseFunc(vfunc, key string) (v ValidFunc, err error) {
|
|||||||
err = fmt.Errorf("%s require %d parameters", vfunc, num)
|
err = fmt.Errorf("%s require %d parameters", vfunc, num)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
v = ValidFunc{vfunc, []interface{}{key + "." + vfunc}}
|
v = ValidFunc{vfunc, []interface{}{key + "." + vfunc + "." + label}}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +213,7 @@ func parseFunc(vfunc, key string) (v ValidFunc, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tParams, err := trim(name, key+"."+name, params)
|
tParams, err := trim(name, key+"."+ name + "." + label, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -267,15 +267,16 @@ func (v *Validation) apply(chk Validator, obj interface{}) *Result {
|
|||||||
key := chk.GetKey()
|
key := chk.GetKey()
|
||||||
Name := key
|
Name := key
|
||||||
Field := ""
|
Field := ""
|
||||||
|
Label := ""
|
||||||
parts := strings.Split(key, ".")
|
parts := strings.Split(key, ".")
|
||||||
if len(parts) == 2 {
|
if len(parts) == 3 {
|
||||||
Field = parts[0]
|
Field = parts[0]
|
||||||
Name = parts[1]
|
Name = parts[1]
|
||||||
|
Label = parts[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
err := &Error{
|
err := &Error{
|
||||||
Message: chk.DefaultMessage(),
|
Message: Label + chk.DefaultMessage(),
|
||||||
Key: key,
|
Key: key,
|
||||||
Name: Name,
|
Name: Name,
|
||||||
Field: Field,
|
Field: Field,
|
||||||
@ -298,7 +299,7 @@ func (v *Validation) AddError(key, message string) {
|
|||||||
Field := ""
|
Field := ""
|
||||||
|
|
||||||
parts := strings.Split(key, ".")
|
parts := strings.Split(key, ".")
|
||||||
if len(parts) == 2 {
|
if len(parts) == 3 {
|
||||||
Field = parts[0]
|
Field = parts[0]
|
||||||
Name = parts[1]
|
Name = parts[1]
|
||||||
}
|
}
|
||||||
|
@ -268,6 +268,30 @@ func TestMobile(t *testing.T) {
|
|||||||
if !valid.Mobile("+8614700008888", "mobile").Ok {
|
if !valid.Mobile("+8614700008888", "mobile").Ok {
|
||||||
t.Error("\"+8614700008888\" is a valid mobile phone number should be true")
|
t.Error("\"+8614700008888\" is a valid mobile phone number should be true")
|
||||||
}
|
}
|
||||||
|
if !valid.Mobile("17300008888", "mobile").Ok {
|
||||||
|
t.Error("\"17300008888\" is a valid mobile phone number should be true")
|
||||||
|
}
|
||||||
|
if !valid.Mobile("+8617100008888", "mobile").Ok {
|
||||||
|
t.Error("\"+8617100008888\" is a valid mobile phone number should be true")
|
||||||
|
}
|
||||||
|
if !valid.Mobile("8617500008888", "mobile").Ok {
|
||||||
|
t.Error("\"8617500008888\" is a valid mobile phone number should be true")
|
||||||
|
}
|
||||||
|
if valid.Mobile("8617400008888", "mobile").Ok {
|
||||||
|
t.Error("\"8617400008888\" is a valid mobile phone number should be false")
|
||||||
|
}
|
||||||
|
if !valid.Mobile("16200008888", "mobile").Ok {
|
||||||
|
t.Error("\"16200008888\" is a valid mobile phone number should be true")
|
||||||
|
}
|
||||||
|
if !valid.Mobile("16500008888", "mobile").Ok {
|
||||||
|
t.Error("\"16500008888\" is a valid mobile phone number should be true")
|
||||||
|
}
|
||||||
|
if !valid.Mobile("16600008888", "mobile").Ok {
|
||||||
|
t.Error("\"16600008888\" is a valid mobile phone number should be true")
|
||||||
|
}
|
||||||
|
if !valid.Mobile("16700008888", "mobile").Ok {
|
||||||
|
t.Error("\"16700008888\" is a valid mobile phone number should be true")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTel(t *testing.T) {
|
func TestTel(t *testing.T) {
|
||||||
@ -453,7 +477,7 @@ func TestPointer(t *testing.T) {
|
|||||||
|
|
||||||
u := User{
|
u := User{
|
||||||
ReqEmail: nil,
|
ReqEmail: nil,
|
||||||
Email: nil,
|
Email: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
valid := Validation{}
|
valid := Validation{}
|
||||||
@ -468,7 +492,7 @@ func TestPointer(t *testing.T) {
|
|||||||
validEmail := "a@a.com"
|
validEmail := "a@a.com"
|
||||||
u = User{
|
u = User{
|
||||||
ReqEmail: &validEmail,
|
ReqEmail: &validEmail,
|
||||||
Email: nil,
|
Email: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
valid = Validation{RequiredFirst: true}
|
valid = Validation{RequiredFirst: true}
|
||||||
@ -482,7 +506,7 @@ func TestPointer(t *testing.T) {
|
|||||||
|
|
||||||
u = User{
|
u = User{
|
||||||
ReqEmail: &validEmail,
|
ReqEmail: &validEmail,
|
||||||
Email: nil,
|
Email: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
valid = Validation{}
|
valid = Validation{}
|
||||||
@ -497,7 +521,7 @@ func TestPointer(t *testing.T) {
|
|||||||
invalidEmail := "a@a"
|
invalidEmail := "a@a"
|
||||||
u = User{
|
u = User{
|
||||||
ReqEmail: &validEmail,
|
ReqEmail: &validEmail,
|
||||||
Email: &invalidEmail,
|
Email: &invalidEmail,
|
||||||
}
|
}
|
||||||
|
|
||||||
valid = Validation{RequiredFirst: true}
|
valid = Validation{RequiredFirst: true}
|
||||||
@ -511,7 +535,7 @@ func TestPointer(t *testing.T) {
|
|||||||
|
|
||||||
u = User{
|
u = User{
|
||||||
ReqEmail: &validEmail,
|
ReqEmail: &validEmail,
|
||||||
Email: &invalidEmail,
|
Email: &invalidEmail,
|
||||||
}
|
}
|
||||||
|
|
||||||
valid = Validation{}
|
valid = Validation{}
|
||||||
@ -524,19 +548,18 @@ func TestPointer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestCanSkipAlso(t *testing.T) {
|
func TestCanSkipAlso(t *testing.T) {
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int
|
ID int
|
||||||
|
|
||||||
Email string `valid:"Email"`
|
Email string `valid:"Email"`
|
||||||
ReqEmail string `valid:"Required;Email"`
|
ReqEmail string `valid:"Required;Email"`
|
||||||
MatchRange int `valid:"Range(10, 20)"`
|
MatchRange int `valid:"Range(10, 20)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
u := User{
|
u := User{
|
||||||
ReqEmail: "a@a.com",
|
ReqEmail: "a@a.com",
|
||||||
Email: "",
|
Email: "",
|
||||||
MatchRange: 0,
|
MatchRange: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -560,4 +583,3 @@ func TestCanSkipAlso(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -632,7 +632,7 @@ func (b Base64) GetLimitValue() interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// just for chinese mobile phone number
|
// just for chinese mobile phone number
|
||||||
var mobilePattern = regexp.MustCompile(`^((\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][06789]|[4][579]))\d{8}$`)
|
var mobilePattern = regexp.MustCompile(`^((\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][01356789]|[4][579]|[6][2567]))\d{8}$`)
|
||||||
|
|
||||||
// Mobile check struct
|
// Mobile check struct
|
||||||
type Mobile struct {
|
type Mobile struct {
|
||||||
|
28
vendor/github.com/Knetic/govaluate/.gitignore
generated
vendored
28
vendor/github.com/Knetic/govaluate/.gitignore
generated
vendored
@ -1,28 +0,0 @@
|
|||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
*.test
|
|
||||||
coverage.out
|
|
||||||
|
|
||||||
manual_test.go
|
|
||||||
*.out
|
|
||||||
*.err
|
|
10
vendor/github.com/Knetic/govaluate/.travis.yml
generated
vendored
10
vendor/github.com/Knetic/govaluate/.travis.yml
generated
vendored
@ -1,10 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
script: ./test.sh
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.2
|
|
||||||
- 1.3
|
|
||||||
- 1.4
|
|
||||||
- 1.5
|
|
||||||
- 1.6
|
|
12
vendor/github.com/Knetic/govaluate/CONTRIBUTORS
generated
vendored
12
vendor/github.com/Knetic/govaluate/CONTRIBUTORS
generated
vendored
@ -1,12 +0,0 @@
|
|||||||
This library was authored by George Lester, and contains contributions from:
|
|
||||||
|
|
||||||
vjeantet (regex support)
|
|
||||||
iasci (ternary operator)
|
|
||||||
oxtoacart (parameter structures, deferred parameter retrieval)
|
|
||||||
wmiller848 (bitwise operators)
|
|
||||||
prashantv (optimization of bools)
|
|
||||||
dpaolella (exposure of variables used in an expression)
|
|
||||||
benpaxton (fix for missing type checks during literal elide process)
|
|
||||||
abrander (panic-finding testing tool)
|
|
||||||
xfennec (fix for dates being parsed in the current Location)
|
|
||||||
bgaifullin (lifting restriction on complex/struct types)
|
|
272
vendor/github.com/Knetic/govaluate/EvaluableExpression.go
generated
vendored
272
vendor/github.com/Knetic/govaluate/EvaluableExpression.go
generated
vendored
@ -1,272 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const isoDateFormat string = "2006-01-02T15:04:05.999999999Z0700"
|
|
||||||
const shortCircuitHolder int = -1
|
|
||||||
|
|
||||||
var DUMMY_PARAMETERS = MapParameters(map[string]interface{}{})
|
|
||||||
|
|
||||||
/*
|
|
||||||
EvaluableExpression represents a set of ExpressionTokens which, taken together,
|
|
||||||
are an expression that can be evaluated down into a single value.
|
|
||||||
*/
|
|
||||||
type EvaluableExpression struct {
|
|
||||||
|
|
||||||
/*
|
|
||||||
Represents the query format used to output dates. Typically only used when creating SQL or Mongo queries from an expression.
|
|
||||||
Defaults to the complete ISO8601 format, including nanoseconds.
|
|
||||||
*/
|
|
||||||
QueryDateFormat string
|
|
||||||
|
|
||||||
/*
|
|
||||||
Whether or not to safely check types when evaluating.
|
|
||||||
If true, this library will return error messages when invalid types are used.
|
|
||||||
If false, the library will panic when operators encounter types they can't use.
|
|
||||||
|
|
||||||
This is exclusively for users who need to squeeze every ounce of speed out of the library as they can,
|
|
||||||
and you should only set this to false if you know exactly what you're doing.
|
|
||||||
*/
|
|
||||||
ChecksTypes bool
|
|
||||||
|
|
||||||
tokens []ExpressionToken
|
|
||||||
evaluationStages *evaluationStage
|
|
||||||
inputExpression string
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Parses a new EvaluableExpression from the given [expression] string.
|
|
||||||
Returns an error if the given expression has invalid syntax.
|
|
||||||
*/
|
|
||||||
func NewEvaluableExpression(expression string) (*EvaluableExpression, error) {
|
|
||||||
|
|
||||||
functions := make(map[string]ExpressionFunction)
|
|
||||||
return NewEvaluableExpressionWithFunctions(expression, functions)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Similar to [NewEvaluableExpression], except that instead of a string, an already-tokenized expression is given.
|
|
||||||
This is useful in cases where you may be generating an expression automatically, or using some other parser (e.g., to parse from a query language)
|
|
||||||
*/
|
|
||||||
func NewEvaluableExpressionFromTokens(tokens []ExpressionToken) (*EvaluableExpression, error) {
|
|
||||||
|
|
||||||
var ret *EvaluableExpression
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ret = new(EvaluableExpression)
|
|
||||||
ret.QueryDateFormat = isoDateFormat
|
|
||||||
|
|
||||||
err = checkBalance(tokens)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = checkExpressionSyntax(tokens)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.tokens, err = optimizeTokens(tokens)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.evaluationStages, err = planStages(ret.tokens)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.ChecksTypes = true
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Similar to [NewEvaluableExpression], except enables the use of user-defined functions.
|
|
||||||
Functions passed into this will be available to the expression.
|
|
||||||
*/
|
|
||||||
func NewEvaluableExpressionWithFunctions(expression string, functions map[string]ExpressionFunction) (*EvaluableExpression, error) {
|
|
||||||
|
|
||||||
var ret *EvaluableExpression
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ret = new(EvaluableExpression)
|
|
||||||
ret.QueryDateFormat = isoDateFormat
|
|
||||||
ret.inputExpression = expression
|
|
||||||
|
|
||||||
ret.tokens, err = parseTokens(expression, functions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = checkBalance(ret.tokens)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = checkExpressionSyntax(ret.tokens)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.tokens, err = optimizeTokens(ret.tokens)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.evaluationStages, err = planStages(ret.tokens)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.ChecksTypes = true
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Same as `Eval`, but automatically wraps a map of parameters into a `govalute.Parameters` structure.
|
|
||||||
*/
|
|
||||||
func (this EvaluableExpression) Evaluate(parameters map[string]interface{}) (interface{}, error) {
|
|
||||||
|
|
||||||
if parameters == nil {
|
|
||||||
return this.Eval(nil)
|
|
||||||
}
|
|
||||||
return this.Eval(MapParameters(parameters))
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Runs the entire expression using the given [parameters].
|
|
||||||
e.g., If the expression contains a reference to the variable "foo", it will be taken from `parameters.Get("foo")`.
|
|
||||||
|
|
||||||
This function returns errors if the combination of expression and parameters cannot be run,
|
|
||||||
such as if a variable in the expression is not present in [parameters].
|
|
||||||
|
|
||||||
In all non-error circumstances, this returns the single value result of the expression and parameters given.
|
|
||||||
e.g., if the expression is "1 + 1", this will return 2.0.
|
|
||||||
e.g., if the expression is "foo + 1" and parameters contains "foo" = 2, this will return 3.0
|
|
||||||
*/
|
|
||||||
func (this EvaluableExpression) Eval(parameters Parameters) (interface{}, error) {
|
|
||||||
|
|
||||||
if this.evaluationStages == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if parameters != nil {
|
|
||||||
parameters = &sanitizedParameters{parameters}
|
|
||||||
}
|
|
||||||
return this.evaluateStage(this.evaluationStages, parameters)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this EvaluableExpression) evaluateStage(stage *evaluationStage, parameters Parameters) (interface{}, error) {
|
|
||||||
|
|
||||||
var left, right interface{}
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if stage.leftStage != nil {
|
|
||||||
left, err = this.evaluateStage(stage.leftStage, parameters)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if stage.isShortCircuitable() {
|
|
||||||
switch stage.symbol {
|
|
||||||
case AND:
|
|
||||||
if left == false {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
case OR:
|
|
||||||
if left == true {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
case COALESCE:
|
|
||||||
if left != nil {
|
|
||||||
return left, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case TERNARY_TRUE:
|
|
||||||
if left == false {
|
|
||||||
right = shortCircuitHolder
|
|
||||||
}
|
|
||||||
case TERNARY_FALSE:
|
|
||||||
if left != nil {
|
|
||||||
right = shortCircuitHolder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if right != shortCircuitHolder && stage.rightStage != nil {
|
|
||||||
right, err = this.evaluateStage(stage.rightStage, parameters)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if this.ChecksTypes {
|
|
||||||
if stage.typeCheck == nil {
|
|
||||||
|
|
||||||
err = typeCheck(stage.leftTypeCheck, left, stage.symbol, stage.typeErrorFormat)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = typeCheck(stage.rightTypeCheck, right, stage.symbol, stage.typeErrorFormat)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// special case where the type check needs to know both sides to determine if the operator can handle it
|
|
||||||
if !stage.typeCheck(left, right) {
|
|
||||||
errorMsg := fmt.Sprintf(stage.typeErrorFormat, left, stage.symbol.String())
|
|
||||||
return nil, errors.New(errorMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stage.operator(left, right, parameters)
|
|
||||||
}
|
|
||||||
|
|
||||||
func typeCheck(check stageTypeCheck, value interface{}, symbol OperatorSymbol, format string) error {
|
|
||||||
|
|
||||||
if check == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if check(value) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
errorMsg := fmt.Sprintf(format, value, symbol.String())
|
|
||||||
return errors.New(errorMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Returns an array representing the ExpressionTokens that make up this expression.
|
|
||||||
*/
|
|
||||||
func (this EvaluableExpression) Tokens() []ExpressionToken {
|
|
||||||
|
|
||||||
return this.tokens
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Returns the original expression used to create this EvaluableExpression.
|
|
||||||
*/
|
|
||||||
func (this EvaluableExpression) String() string {
|
|
||||||
|
|
||||||
return this.inputExpression
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Returns an array representing the variables contained in this EvaluableExpression.
|
|
||||||
*/
|
|
||||||
func (this EvaluableExpression) Vars() []string {
|
|
||||||
var varlist []string
|
|
||||||
for _, val := range this.Tokens() {
|
|
||||||
if val.Kind == VARIABLE {
|
|
||||||
varlist = append(varlist, val.Value.(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return varlist
|
|
||||||
}
|
|
167
vendor/github.com/Knetic/govaluate/EvaluableExpression_sql.go
generated
vendored
167
vendor/github.com/Knetic/govaluate/EvaluableExpression_sql.go
generated
vendored
@ -1,167 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Returns a string representing this expression as if it were written in SQL.
|
|
||||||
This function assumes that all parameters exist within the same table, and that the table essentially represents
|
|
||||||
a serialized object of some sort (e.g., hibernate).
|
|
||||||
If your data model is more normalized, you may need to consider iterating through each actual token given by `Tokens()`
|
|
||||||
to create your query.
|
|
||||||
|
|
||||||
Boolean values are considered to be "1" for true, "0" for false.
|
|
||||||
|
|
||||||
Times are formatted according to this.QueryDateFormat.
|
|
||||||
*/
|
|
||||||
func (this EvaluableExpression) ToSQLQuery() (string, error) {
|
|
||||||
|
|
||||||
var stream *tokenStream
|
|
||||||
var transactions *expressionOutputStream
|
|
||||||
var transaction string
|
|
||||||
var err error
|
|
||||||
|
|
||||||
stream = newTokenStream(this.tokens)
|
|
||||||
transactions = new(expressionOutputStream)
|
|
||||||
|
|
||||||
for stream.hasNext() {
|
|
||||||
|
|
||||||
transaction, err = this.findNextSQLString(stream, transactions)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
transactions.add(transaction)
|
|
||||||
}
|
|
||||||
|
|
||||||
return transactions.createString(" "), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this EvaluableExpression) findNextSQLString(stream *tokenStream, transactions *expressionOutputStream) (string, error) {
|
|
||||||
|
|
||||||
var token ExpressionToken
|
|
||||||
var ret string
|
|
||||||
|
|
||||||
token = stream.next()
|
|
||||||
|
|
||||||
switch token.Kind {
|
|
||||||
|
|
||||||
case STRING:
|
|
||||||
ret = fmt.Sprintf("'%v'", token.Value)
|
|
||||||
case PATTERN:
|
|
||||||
ret = fmt.Sprintf("'%s'", token.Value.(*regexp.Regexp).String())
|
|
||||||
case TIME:
|
|
||||||
ret = fmt.Sprintf("'%s'", token.Value.(time.Time).Format(this.QueryDateFormat))
|
|
||||||
|
|
||||||
case LOGICALOP:
|
|
||||||
switch logicalSymbols[token.Value.(string)] {
|
|
||||||
|
|
||||||
case AND:
|
|
||||||
ret = "AND"
|
|
||||||
case OR:
|
|
||||||
ret = "OR"
|
|
||||||
}
|
|
||||||
|
|
||||||
case BOOLEAN:
|
|
||||||
if token.Value.(bool) {
|
|
||||||
ret = "1"
|
|
||||||
} else {
|
|
||||||
ret = "0"
|
|
||||||
}
|
|
||||||
|
|
||||||
case VARIABLE:
|
|
||||||
ret = fmt.Sprintf("[%s]", token.Value.(string))
|
|
||||||
|
|
||||||
case NUMERIC:
|
|
||||||
ret = fmt.Sprintf("%g", token.Value.(float64))
|
|
||||||
|
|
||||||
case COMPARATOR:
|
|
||||||
switch comparatorSymbols[token.Value.(string)] {
|
|
||||||
|
|
||||||
case EQ:
|
|
||||||
ret = "="
|
|
||||||
case NEQ:
|
|
||||||
ret = "<>"
|
|
||||||
case REQ:
|
|
||||||
ret = "RLIKE"
|
|
||||||
case NREQ:
|
|
||||||
ret = "NOT RLIKE"
|
|
||||||
default:
|
|
||||||
ret = fmt.Sprintf("%s", token.Value.(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
case TERNARY:
|
|
||||||
|
|
||||||
switch ternarySymbols[token.Value.(string)] {
|
|
||||||
|
|
||||||
case COALESCE:
|
|
||||||
|
|
||||||
left := transactions.rollback()
|
|
||||||
right, err := this.findNextSQLString(stream, transactions)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = fmt.Sprintf("COALESCE(%v, %v)", left, right)
|
|
||||||
case TERNARY_TRUE:
|
|
||||||
fallthrough
|
|
||||||
case TERNARY_FALSE:
|
|
||||||
return "", errors.New("Ternary operators are unsupported in SQL output")
|
|
||||||
}
|
|
||||||
case PREFIX:
|
|
||||||
switch prefixSymbols[token.Value.(string)] {
|
|
||||||
|
|
||||||
case INVERT:
|
|
||||||
ret = fmt.Sprintf("NOT")
|
|
||||||
default:
|
|
||||||
|
|
||||||
right, err := this.findNextSQLString(stream, transactions)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = fmt.Sprintf("%s%s", token.Value.(string), right)
|
|
||||||
}
|
|
||||||
case MODIFIER:
|
|
||||||
|
|
||||||
switch modifierSymbols[token.Value.(string)] {
|
|
||||||
|
|
||||||
case EXPONENT:
|
|
||||||
|
|
||||||
left := transactions.rollback()
|
|
||||||
right, err := this.findNextSQLString(stream, transactions)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = fmt.Sprintf("POW(%s, %s)", left, right)
|
|
||||||
case MODULUS:
|
|
||||||
|
|
||||||
left := transactions.rollback()
|
|
||||||
right, err := this.findNextSQLString(stream, transactions)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = fmt.Sprintf("MOD(%s, %s)", left, right)
|
|
||||||
default:
|
|
||||||
ret = fmt.Sprintf("%s", token.Value.(string))
|
|
||||||
}
|
|
||||||
case CLAUSE:
|
|
||||||
ret = "("
|
|
||||||
case CLAUSE_CLOSE:
|
|
||||||
ret = ")"
|
|
||||||
case SEPARATOR:
|
|
||||||
ret = ","
|
|
||||||
|
|
||||||
default:
|
|
||||||
errorMsg := fmt.Sprintf("Unrecognized query token '%s' of kind '%s'", token.Value, token.Kind)
|
|
||||||
return "", errors.New(errorMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
9
vendor/github.com/Knetic/govaluate/ExpressionToken.go
generated
vendored
9
vendor/github.com/Knetic/govaluate/ExpressionToken.go
generated
vendored
@ -1,9 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
/*
|
|
||||||
Represents a single parsed token.
|
|
||||||
*/
|
|
||||||
type ExpressionToken struct {
|
|
||||||
Kind TokenKind
|
|
||||||
Value interface{}
|
|
||||||
}
|
|
21
vendor/github.com/Knetic/govaluate/LICENSE
generated
vendored
21
vendor/github.com/Knetic/govaluate/LICENSE
generated
vendored
@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014-2016 George Lester
|
|
||||||
|
|
||||||
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.
|
|
176
vendor/github.com/Knetic/govaluate/MANUAL.md
generated
vendored
176
vendor/github.com/Knetic/govaluate/MANUAL.md
generated
vendored
@ -1,176 +0,0 @@
|
|||||||
govaluate
|
|
||||||
====
|
|
||||||
|
|
||||||
This library contains quite a lot of functionality, this document is meant to be formal documentation on the operators and features of it.
|
|
||||||
Some of this documentation may duplicate what's in README.md, but should never conflict.
|
|
||||||
|
|
||||||
# Types
|
|
||||||
|
|
||||||
This library only officially deals with four types; `float64`, `bool`, `string`, and arrays.
|
|
||||||
|
|
||||||
All numeric literals, with or without a radix, will be converted to `float64` for evaluation. For instance; in practice, there is no difference between the literals "1.0" and "1", they both end up as `float64`. This matters to users because if you intend to return numeric values from your expressions, then the returned value will be `float64`, not any other numeric type.
|
|
||||||
|
|
||||||
Any string _literal_ (not parameter) which is interpretable as a date will be converted to a `float64` representation of that date's unix time. Any `time.Time` parameters will not be operable with these date literals; such parameters will need to use the `time.Time.Unix()` method to get a numeric representation.
|
|
||||||
|
|
||||||
Arrays are untyped, and can be mixed-type. Internally they're all just `interface{}`. Only two operators can interact with arrays, `IN` and `,`. All other operators will refuse to operate on arrays.
|
|
||||||
|
|
||||||
# Operators
|
|
||||||
|
|
||||||
## Modifiers
|
|
||||||
|
|
||||||
### Addition, concatenation `+`
|
|
||||||
|
|
||||||
If either left or right sides of the `+` operator are a `string`, then this operator will perform string concatenation and return that result. If neither are string, then both must be numeric, and this will return a numeric result.
|
|
||||||
|
|
||||||
Any other case is invalid.
|
|
||||||
|
|
||||||
### Arithmetic `-` `*` `/` `**` `%`
|
|
||||||
|
|
||||||
`**` refers to "take to the power of". For instance, `3 ** 4` == 81.
|
|
||||||
|
|
||||||
* _Left side_: numeric
|
|
||||||
* _Right side_: numeric
|
|
||||||
* _Returns_: numeric
|
|
||||||
|
|
||||||
### Bitwise shifts, masks `>>` `<<` `|` `&` `^`
|
|
||||||
|
|
||||||
All of these operators convert their `float64` left and right sides to `int64`, perform their operation, and then convert back.
|
|
||||||
Given how this library assumes numeric are represented (as `float64`), it is unlikely that this behavior will change, even though it may cause havoc with extremely large or small numbers.
|
|
||||||
|
|
||||||
* _Left side_: numeric
|
|
||||||
* _Right side_: numeric
|
|
||||||
* _Returns_: numeric
|
|
||||||
|
|
||||||
### Negation `-`
|
|
||||||
|
|
||||||
Prefix only. This can never have a left-hand value.
|
|
||||||
|
|
||||||
* _Right side_: numeric
|
|
||||||
* _Returns_: numeric
|
|
||||||
|
|
||||||
### Inversion `!`
|
|
||||||
|
|
||||||
Prefix only. This can never have a left-hand value.
|
|
||||||
|
|
||||||
* _Right side_: bool
|
|
||||||
* _Returns_: bool
|
|
||||||
|
|
||||||
### Bitwise NOT `~`
|
|
||||||
|
|
||||||
Prefix only. This can never have a left-hand value.
|
|
||||||
|
|
||||||
* _Right side_: numeric
|
|
||||||
* _Returns_: numeric
|
|
||||||
|
|
||||||
## Logical Operators
|
|
||||||
|
|
||||||
For all logical operators, this library will short-circuit the operation if the left-hand side is sufficient to determine what to do. For instance, `true || expensiveOperation()` will not actually call `expensiveOperation()`, since it knows the left-hand side is `true`.
|
|
||||||
|
|
||||||
### Logical AND/OR `&&` `||`
|
|
||||||
|
|
||||||
* _Left side_: bool
|
|
||||||
* _Right side_: bool
|
|
||||||
* _Returns_: bool
|
|
||||||
|
|
||||||
### Ternary true `?`
|
|
||||||
|
|
||||||
Checks if the left side is `true`. If so, returns the right side. If the left side is `false`, returns `nil`.
|
|
||||||
In practice, this is commonly used with the other ternary operator.
|
|
||||||
|
|
||||||
* _Left side_: bool
|
|
||||||
* _Right side_: Any type.
|
|
||||||
* _Returns_: Right side or `nil`
|
|
||||||
|
|
||||||
### Ternary false `:`
|
|
||||||
|
|
||||||
Checks if the left side is `nil`. If so, returns the right side. If the left side is non-nil, returns the left side.
|
|
||||||
In practice, this is commonly used with the other ternary operator.
|
|
||||||
|
|
||||||
* _Left side_: Any type.
|
|
||||||
* _Right side_: Any type.
|
|
||||||
* _Returns_: Right side or `nil`
|
|
||||||
|
|
||||||
### Null coalescence `??`
|
|
||||||
|
|
||||||
Similar to the C# operator. If the left value is non-nil, it returns that. If not, then the right-value is returned.
|
|
||||||
|
|
||||||
* _Left side_: Any type.
|
|
||||||
* _Right side_: Any type.
|
|
||||||
* _Returns_: No specific type - whichever is passed to it.
|
|
||||||
|
|
||||||
## Comparators
|
|
||||||
|
|
||||||
### Numeric/lexicographic comparators `>` `<` `>=` `<=`
|
|
||||||
|
|
||||||
If both sides are numeric, this returns the usual greater/lesser behavior that would be expected.
|
|
||||||
If both sides are string, this returns the lexicographic comparison of the strings. This uses Go's standard lexicographic compare.
|
|
||||||
|
|
||||||
* _Accepts_: Left and right side must either be both string, or both numeric.
|
|
||||||
* _Returns_: bool
|
|
||||||
|
|
||||||
### Regex comparators `=~` `!~`
|
|
||||||
|
|
||||||
These use go's standard `regexp` flavor of regex. The left side is expected to be the candidate string, the right side is the pattern. `=~` returns whether or not the candidate string matches the regex pattern given on the right. `!~` is the inverted version of the same logic.
|
|
||||||
|
|
||||||
* _Left side_: string
|
|
||||||
* _Right side_: string
|
|
||||||
* _Returns_: bool
|
|
||||||
|
|
||||||
## Arrays
|
|
||||||
|
|
||||||
### Separator `,`
|
|
||||||
|
|
||||||
The separator, always paired with parenthesis, creates arrays. It must always have both a left and right-hand value, so for instance `(, 0)` and `(0,)` are invalid uses of it.
|
|
||||||
|
|
||||||
Again, this should always be used with parenthesis; like `(1, 2, 3, 4)`.
|
|
||||||
|
|
||||||
### Membership `IN`
|
|
||||||
|
|
||||||
The only operator with a text name, this operator checks the right-hand side array to see if it contains a value that is equal to the left-side value.
|
|
||||||
Equality is determined by the use of the `==` operator, and this library doesn't check types between the values. Any two values, when cast to `interface{}`, and can still be checked for equality with `==` will act as expected.
|
|
||||||
|
|
||||||
Note that you can use a parameter for the array, but it must be an `[]interface{}`.
|
|
||||||
|
|
||||||
* _Left side_: Any type.
|
|
||||||
* _Right side_: array
|
|
||||||
* _Returns_: bool
|
|
||||||
|
|
||||||
# Parameters
|
|
||||||
|
|
||||||
Parameters must be passed in every time the expression is evaluated. Parameters can be of any type, but will not cause errors unless actually used in an erroneous way. There is no difference in behavior for any of the above operators for parameters - they are type checked when used.
|
|
||||||
|
|
||||||
All `int` and `float` values of any width will be converted to `float64` before use.
|
|
||||||
|
|
||||||
At no point is the parameter structure, or any value thereof, modified by this library.
|
|
||||||
|
|
||||||
## Alternates to maps
|
|
||||||
|
|
||||||
The default form of parameters as a map may not serve your use case. You may have parameters in some other structure, you may want to change the no-parameter-found behavior, or maybe even just have some debugging print statements invoked when a parameter is accessed.
|
|
||||||
|
|
||||||
To do this, define a type that implements the `govaluate.Parameters` interface. When you want to evaluate, instead call `EvaluableExpression.Eval` and pass your parameter structure.
|
|
||||||
|
|
||||||
# Functions
|
|
||||||
|
|
||||||
During expression parsing (_not_ evaluation), a map of functions can be given to `govaluate.NewEvaluableExpressionWithFunctions` (the lengthiest and finest of function names). The resultant expression will be able to invoke those functions during evaluation. Once parsed, an expression cannot have functions added or removed - a new expression will need to be created if you want to change the functions, or behavior of said functions.
|
|
||||||
|
|
||||||
Functions always take the form `<name>(<parameters>)`, including parens. Functions can have an empty list of parameters, like `<name>()`, but still must have parens.
|
|
||||||
|
|
||||||
If the expression contains something that looks like it ought to be a function (such as `foo()`), but no such function was given to it, it will error on parsing.
|
|
||||||
|
|
||||||
Functions must be of type `map[string]govaluate.ExpressionFunction`. `ExpressionFunction`, for brevity, has the following signature:
|
|
||||||
|
|
||||||
`func(args ...interface{}) (interface{}, error)`
|
|
||||||
|
|
||||||
Where `args` is whatever is passed to the function when called. If a non-nil error is returned from a function during evaluation, the evaluation stops and ultimately returns that error to the caller of `Evaluate()` or `Eval()`.
|
|
||||||
|
|
||||||
## Built-in functions
|
|
||||||
|
|
||||||
There aren't any builtin functions. The author is opposed to maintaining a standard library of functions to be used.
|
|
||||||
|
|
||||||
Every use case of this library is different, and even in simple use cases (such as parameters, see above) different users need different behavior, naming, or even functionality. The author prefers that users make their own decisions about what functions they need, and how they operate.
|
|
||||||
|
|
||||||
# Equality
|
|
||||||
|
|
||||||
The `==` and `!=` operators involve a moderately complex workflow. They use [`reflect.DeepEqual`](https://golang.org/pkg/reflect/#DeepEqual). This is for complicated reasons, but there are some types in Go that cannot be compared with the native `==` operator. Arrays, in particular, cannot be compared - Go will panic if you try. One might assume this could be handled with the type checking system in `govaluate`, but unfortunately without reflection there is no way to know if a variable is a slice/array. Worse, structs can be incomparable if they _contain incomparable types_.
|
|
||||||
|
|
||||||
It's all very complicated. Fortunately, Go includes the `reflect.DeepEqual` function to handle all the edge cases. Currently, `govaluate` uses that for all equality/inequality.
|
|
306
vendor/github.com/Knetic/govaluate/OperatorSymbol.go
generated
vendored
306
vendor/github.com/Knetic/govaluate/OperatorSymbol.go
generated
vendored
@ -1,306 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
/*
|
|
||||||
Represents the valid symbols for operators.
|
|
||||||
|
|
||||||
*/
|
|
||||||
type OperatorSymbol int
|
|
||||||
|
|
||||||
const (
|
|
||||||
VALUE OperatorSymbol = iota
|
|
||||||
LITERAL
|
|
||||||
NOOP
|
|
||||||
EQ
|
|
||||||
NEQ
|
|
||||||
GT
|
|
||||||
LT
|
|
||||||
GTE
|
|
||||||
LTE
|
|
||||||
REQ
|
|
||||||
NREQ
|
|
||||||
IN
|
|
||||||
|
|
||||||
AND
|
|
||||||
OR
|
|
||||||
|
|
||||||
PLUS
|
|
||||||
MINUS
|
|
||||||
BITWISE_AND
|
|
||||||
BITWISE_OR
|
|
||||||
BITWISE_XOR
|
|
||||||
BITWISE_LSHIFT
|
|
||||||
BITWISE_RSHIFT
|
|
||||||
MULTIPLY
|
|
||||||
DIVIDE
|
|
||||||
MODULUS
|
|
||||||
EXPONENT
|
|
||||||
|
|
||||||
NEGATE
|
|
||||||
INVERT
|
|
||||||
BITWISE_NOT
|
|
||||||
|
|
||||||
TERNARY_TRUE
|
|
||||||
TERNARY_FALSE
|
|
||||||
COALESCE
|
|
||||||
|
|
||||||
FUNCTIONAL
|
|
||||||
SEPARATE
|
|
||||||
)
|
|
||||||
|
|
||||||
type operatorPrecedence int
|
|
||||||
|
|
||||||
const (
|
|
||||||
noopPrecedence operatorPrecedence = iota
|
|
||||||
valuePrecedence
|
|
||||||
functionalPrecedence
|
|
||||||
prefixPrecedence
|
|
||||||
exponentialPrecedence
|
|
||||||
additivePrecedence
|
|
||||||
bitwisePrecedence
|
|
||||||
bitwiseShiftPrecedence
|
|
||||||
multiplicativePrecedence
|
|
||||||
comparatorPrecedence
|
|
||||||
ternaryPrecedence
|
|
||||||
logicalAndPrecedence
|
|
||||||
logicalOrPrecedence
|
|
||||||
separatePrecedence
|
|
||||||
)
|
|
||||||
|
|
||||||
func findOperatorPrecedenceForSymbol(symbol OperatorSymbol) operatorPrecedence {
|
|
||||||
|
|
||||||
switch symbol {
|
|
||||||
case NOOP:
|
|
||||||
return noopPrecedence
|
|
||||||
case VALUE:
|
|
||||||
return valuePrecedence
|
|
||||||
case EQ:
|
|
||||||
fallthrough
|
|
||||||
case NEQ:
|
|
||||||
fallthrough
|
|
||||||
case GT:
|
|
||||||
fallthrough
|
|
||||||
case LT:
|
|
||||||
fallthrough
|
|
||||||
case GTE:
|
|
||||||
fallthrough
|
|
||||||
case LTE:
|
|
||||||
fallthrough
|
|
||||||
case REQ:
|
|
||||||
fallthrough
|
|
||||||
case NREQ:
|
|
||||||
fallthrough
|
|
||||||
case IN:
|
|
||||||
return comparatorPrecedence
|
|
||||||
case AND:
|
|
||||||
return logicalAndPrecedence
|
|
||||||
case OR:
|
|
||||||
return logicalOrPrecedence
|
|
||||||
case BITWISE_AND:
|
|
||||||
fallthrough
|
|
||||||
case BITWISE_OR:
|
|
||||||
fallthrough
|
|
||||||
case BITWISE_XOR:
|
|
||||||
return bitwisePrecedence
|
|
||||||
case BITWISE_LSHIFT:
|
|
||||||
fallthrough
|
|
||||||
case BITWISE_RSHIFT:
|
|
||||||
return bitwiseShiftPrecedence
|
|
||||||
case PLUS:
|
|
||||||
fallthrough
|
|
||||||
case MINUS:
|
|
||||||
return additivePrecedence
|
|
||||||
case MULTIPLY:
|
|
||||||
fallthrough
|
|
||||||
case DIVIDE:
|
|
||||||
fallthrough
|
|
||||||
case MODULUS:
|
|
||||||
return multiplicativePrecedence
|
|
||||||
case EXPONENT:
|
|
||||||
return exponentialPrecedence
|
|
||||||
case BITWISE_NOT:
|
|
||||||
fallthrough
|
|
||||||
case NEGATE:
|
|
||||||
fallthrough
|
|
||||||
case INVERT:
|
|
||||||
return prefixPrecedence
|
|
||||||
case COALESCE:
|
|
||||||
fallthrough
|
|
||||||
case TERNARY_TRUE:
|
|
||||||
fallthrough
|
|
||||||
case TERNARY_FALSE:
|
|
||||||
return ternaryPrecedence
|
|
||||||
case FUNCTIONAL:
|
|
||||||
return functionalPrecedence
|
|
||||||
case SEPARATE:
|
|
||||||
return separatePrecedence
|
|
||||||
}
|
|
||||||
|
|
||||||
return valuePrecedence
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Map of all valid comparators, and their string equivalents.
|
|
||||||
Used during parsing of expressions to determine if a symbol is, in fact, a comparator.
|
|
||||||
Also used during evaluation to determine exactly which comparator is being used.
|
|
||||||
*/
|
|
||||||
var comparatorSymbols = map[string]OperatorSymbol{
|
|
||||||
"==": EQ,
|
|
||||||
"!=": NEQ,
|
|
||||||
">": GT,
|
|
||||||
">=": GTE,
|
|
||||||
"<": LT,
|
|
||||||
"<=": LTE,
|
|
||||||
"=~": REQ,
|
|
||||||
"!~": NREQ,
|
|
||||||
"in": IN,
|
|
||||||
}
|
|
||||||
|
|
||||||
var logicalSymbols = map[string]OperatorSymbol{
|
|
||||||
"&&": AND,
|
|
||||||
"||": OR,
|
|
||||||
}
|
|
||||||
|
|
||||||
var bitwiseSymbols = map[string]OperatorSymbol{
|
|
||||||
"^": BITWISE_XOR,
|
|
||||||
"&": BITWISE_AND,
|
|
||||||
"|": BITWISE_OR,
|
|
||||||
}
|
|
||||||
|
|
||||||
var bitwiseShiftSymbols = map[string]OperatorSymbol{
|
|
||||||
">>": BITWISE_RSHIFT,
|
|
||||||
"<<": BITWISE_LSHIFT,
|
|
||||||
}
|
|
||||||
|
|
||||||
var additiveSymbols = map[string]OperatorSymbol{
|
|
||||||
"+": PLUS,
|
|
||||||
"-": MINUS,
|
|
||||||
}
|
|
||||||
|
|
||||||
var multiplicativeSymbols = map[string]OperatorSymbol{
|
|
||||||
"*": MULTIPLY,
|
|
||||||
"/": DIVIDE,
|
|
||||||
"%": MODULUS,
|
|
||||||
}
|
|
||||||
|
|
||||||
var exponentialSymbolsS = map[string]OperatorSymbol{
|
|
||||||
"**": EXPONENT,
|
|
||||||
}
|
|
||||||
|
|
||||||
var prefixSymbols = map[string]OperatorSymbol{
|
|
||||||
"-": NEGATE,
|
|
||||||
"!": INVERT,
|
|
||||||
"~": BITWISE_NOT,
|
|
||||||
}
|
|
||||||
|
|
||||||
var ternarySymbols = map[string]OperatorSymbol{
|
|
||||||
"?": TERNARY_TRUE,
|
|
||||||
":": TERNARY_FALSE,
|
|
||||||
"??": COALESCE,
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is defined separately from additiveSymbols et al because it's needed for parsing, not stage planning.
|
|
||||||
var modifierSymbols = map[string]OperatorSymbol{
|
|
||||||
"+": PLUS,
|
|
||||||
"-": MINUS,
|
|
||||||
"*": MULTIPLY,
|
|
||||||
"/": DIVIDE,
|
|
||||||
"%": MODULUS,
|
|
||||||
"**": EXPONENT,
|
|
||||||
"&": BITWISE_AND,
|
|
||||||
"|": BITWISE_OR,
|
|
||||||
"^": BITWISE_XOR,
|
|
||||||
">>": BITWISE_RSHIFT,
|
|
||||||
"<<": BITWISE_LSHIFT,
|
|
||||||
}
|
|
||||||
|
|
||||||
var separatorSymbols = map[string]OperatorSymbol{
|
|
||||||
",": SEPARATE,
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Returns true if this operator is contained by the given array of candidate symbols.
|
|
||||||
False otherwise.
|
|
||||||
*/
|
|
||||||
func (this OperatorSymbol) IsModifierType(candidate []OperatorSymbol) bool {
|
|
||||||
|
|
||||||
for _, symbolType := range candidate {
|
|
||||||
if this == symbolType {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Generally used when formatting type check errors.
|
|
||||||
We could store the stringified symbol somewhere else and not require a duplicated codeblock to translate
|
|
||||||
OperatorSymbol to string, but that would require more memory, and another field somewhere.
|
|
||||||
Adding operators is rare enough that we just stringify it here instead.
|
|
||||||
*/
|
|
||||||
func (this OperatorSymbol) String() string {
|
|
||||||
|
|
||||||
switch this {
|
|
||||||
case NOOP:
|
|
||||||
return "NOOP"
|
|
||||||
case VALUE:
|
|
||||||
return "VALUE"
|
|
||||||
case EQ:
|
|
||||||
return "="
|
|
||||||
case NEQ:
|
|
||||||
return "!="
|
|
||||||
case GT:
|
|
||||||
return ">"
|
|
||||||
case LT:
|
|
||||||
return "<"
|
|
||||||
case GTE:
|
|
||||||
return ">="
|
|
||||||
case LTE:
|
|
||||||
return "<="
|
|
||||||
case REQ:
|
|
||||||
return "=~"
|
|
||||||
case NREQ:
|
|
||||||
return "!~"
|
|
||||||
case AND:
|
|
||||||
return "&&"
|
|
||||||
case OR:
|
|
||||||
return "||"
|
|
||||||
case IN:
|
|
||||||
return "in"
|
|
||||||
case BITWISE_AND:
|
|
||||||
return "&"
|
|
||||||
case BITWISE_OR:
|
|
||||||
return "|"
|
|
||||||
case BITWISE_XOR:
|
|
||||||
return "^"
|
|
||||||
case BITWISE_LSHIFT:
|
|
||||||
return "<<"
|
|
||||||
case BITWISE_RSHIFT:
|
|
||||||
return ">>"
|
|
||||||
case PLUS:
|
|
||||||
return "+"
|
|
||||||
case MINUS:
|
|
||||||
return "-"
|
|
||||||
case MULTIPLY:
|
|
||||||
return "*"
|
|
||||||
case DIVIDE:
|
|
||||||
return "/"
|
|
||||||
case MODULUS:
|
|
||||||
return "%"
|
|
||||||
case EXPONENT:
|
|
||||||
return "**"
|
|
||||||
case NEGATE:
|
|
||||||
return "-"
|
|
||||||
case INVERT:
|
|
||||||
return "!"
|
|
||||||
case BITWISE_NOT:
|
|
||||||
return "~"
|
|
||||||
case TERNARY_TRUE:
|
|
||||||
return "?"
|
|
||||||
case TERNARY_FALSE:
|
|
||||||
return ":"
|
|
||||||
case COALESCE:
|
|
||||||
return "??"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
210
vendor/github.com/Knetic/govaluate/README.md
generated
vendored
210
vendor/github.com/Knetic/govaluate/README.md
generated
vendored
@ -1,210 +0,0 @@
|
|||||||
govaluate
|
|
||||||
====
|
|
||||||
|
|
||||||
[](https://travis-ci.org/Knetic/govaluate)
|
|
||||||
[](https://godoc.org/github.com/Knetic/govaluate)
|
|
||||||
|
|
||||||
|
|
||||||
Provides support for evaluating arbitrary C-like artithmetic/string expressions.
|
|
||||||
|
|
||||||
Why can't you just write these expressions in code?
|
|
||||||
--
|
|
||||||
|
|
||||||
Sometimes, you can't know ahead-of-time what an expression will look like, or you want those expressions to be configurable.
|
|
||||||
Perhaps you've got a set of data running through your application, and you want to allow your users to specify some validations to run on it before committing it to a database. Or maybe you've written a monitoring framework which is capable of gathering a bunch of metrics, then evaluating a few expressions to see if any metrics should be alerted upon, but the conditions for alerting are different for each monitor.
|
|
||||||
|
|
||||||
A lot of people wind up writing their own half-baked style of evaluation language that fits their needs, but isn't complete. Or they wind up baking the expression into the actual executable, even if they know it's subject to change. These strategies may work, but they take time to implement, time for users to learn, and induce technical debt as requirements change. This library is meant to cover all the normal C-like expressions, so that you don't have to reinvent one of the oldest wheels on a computer.
|
|
||||||
|
|
||||||
How do I use it?
|
|
||||||
--
|
|
||||||
|
|
||||||
You create a new EvaluableExpression, then call "Evaluate" on it.
|
|
||||||
|
|
||||||
```go
|
|
||||||
expression, err := govaluate.NewEvaluableExpression("10 > 0");
|
|
||||||
result, err := expression.Evaluate(nil);
|
|
||||||
// result is now set to "true", the bool value.
|
|
||||||
```
|
|
||||||
|
|
||||||
Cool, but how about with parameters?
|
|
||||||
|
|
||||||
```go
|
|
||||||
expression, err := govaluate.NewEvaluableExpression("foo > 0");
|
|
||||||
|
|
||||||
parameters := make(map[string]interface{}, 8)
|
|
||||||
parameters["foo"] = -1;
|
|
||||||
|
|
||||||
result, err := expression.Evaluate(parameters);
|
|
||||||
// result is now set to "false", the bool value.
|
|
||||||
```
|
|
||||||
|
|
||||||
That's cool, but we can almost certainly have done all that in code. What about a complex use case that involves some math?
|
|
||||||
|
|
||||||
```go
|
|
||||||
expression, err := govaluate.NewEvaluableExpression("(requests_made * requests_succeeded / 100) >= 90");
|
|
||||||
|
|
||||||
parameters := make(map[string]interface{}, 8)
|
|
||||||
parameters["requests_made"] = 100;
|
|
||||||
parameters["requests_succeeded"] = 80;
|
|
||||||
|
|
||||||
result, err := expression.Evaluate(parameters);
|
|
||||||
// result is now set to "false", the bool value.
|
|
||||||
```
|
|
||||||
|
|
||||||
Or maybe you want to check the status of an alive check ("smoketest") page, which will be a string?
|
|
||||||
|
|
||||||
```go
|
|
||||||
expression, err := govaluate.NewEvaluableExpression("http_response_body == 'service is ok'");
|
|
||||||
|
|
||||||
parameters := make(map[string]interface{}, 8)
|
|
||||||
parameters["http_response_body"] = "service is ok";
|
|
||||||
|
|
||||||
result, err := expression.Evaluate(parameters);
|
|
||||||
// result is now set to "true", the bool value.
|
|
||||||
```
|
|
||||||
|
|
||||||
These examples have all returned boolean values, but it's equally possible to return numeric ones.
|
|
||||||
|
|
||||||
```go
|
|
||||||
expression, err := govaluate.NewEvaluableExpression("(mem_used / total_mem) * 100");
|
|
||||||
|
|
||||||
parameters := make(map[string]interface{}, 8)
|
|
||||||
parameters["total_mem"] = 1024;
|
|
||||||
parameters["mem_used"] = 512;
|
|
||||||
|
|
||||||
result, err := expression.Evaluate(parameters);
|
|
||||||
// result is now set to "50.0", the float64 value.
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also do date parsing, though the formats are somewhat limited. Stick to RF3339, ISO8061, unix date, or ruby date formats. If you're having trouble getting a date string to parse, check the list of formats actually used: [parsing.go:248](https://github.com/Knetic/govaluate/blob/0580e9b47a69125afa0e4ebd1cf93c49eb5a43ec/parsing.go#L258).
|
|
||||||
|
|
||||||
```go
|
|
||||||
expression, err := govaluate.NewEvaluableExpression("'2014-01-02' > '2014-01-01 23:59:59'");
|
|
||||||
result, err := expression.Evaluate(nil);
|
|
||||||
|
|
||||||
// result is now set to true
|
|
||||||
```
|
|
||||||
|
|
||||||
Expressions are parsed once, and can be re-used multiple times. Parsing is the compute-intensive phase of the process, so if you intend to use the same expression with different parameters, just parse it once. Like so;
|
|
||||||
|
|
||||||
```go
|
|
||||||
expression, err := govaluate.NewEvaluableExpression("response_time <= 100");
|
|
||||||
parameters := make(map[string]interface{}, 8)
|
|
||||||
|
|
||||||
for {
|
|
||||||
parameters["response_time"] = pingSomething();
|
|
||||||
result, err := expression.Evaluate(parameters)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The normal C-standard order of operators is respected. When writing an expression, be sure that you either order the operators correctly, or use parenthesis to clarify which portions of an expression should be run first.
|
|
||||||
|
|
||||||
Escaping characters
|
|
||||||
--
|
|
||||||
|
|
||||||
Sometimes you'll have parameters that have spaces, slashes, pluses, ampersands or some other character
|
|
||||||
that this library interprets as something special. For example, the following expression will not
|
|
||||||
act as one might expect:
|
|
||||||
|
|
||||||
"response-time < 100"
|
|
||||||
|
|
||||||
As written, the library will parse it as "[response] minus [time] is less than 100". In reality,
|
|
||||||
"response-time" is meant to be one variable that just happens to have a dash in it.
|
|
||||||
|
|
||||||
There are two ways to work around this. First, you can escape the entire parameter name:
|
|
||||||
|
|
||||||
"[response-time] < 100"
|
|
||||||
|
|
||||||
Or you can use backslashes to escape only the minus sign.
|
|
||||||
|
|
||||||
"response\\-time < 100"
|
|
||||||
|
|
||||||
Backslashes can be used anywhere in an expression to escape the very next character. Square bracketed parameter names can be used instead of plain parameter names at any time.
|
|
||||||
|
|
||||||
Functions
|
|
||||||
--
|
|
||||||
|
|
||||||
You may have cases where you want to call a function on a parameter during execution of the expression. Perhaps you want to aggregate some set of data, but don't know the exact aggregation you want to use until you're writing the expression itself. Or maybe you have a mathematical operation you want to perform, for which there is no operator; like `log` or `tan` or `sqrt`. For cases like this, you can provide a map of functions to `NewEvaluableExpressionWithFunctions`, which will then be able to use them during execution. For instance;
|
|
||||||
|
|
||||||
```go
|
|
||||||
functions := map[string]govaluate.ExpressionFunction {
|
|
||||||
"strlen": func(args ...interface{}) (interface{}, error) {
|
|
||||||
length := len(args[0].(string))
|
|
||||||
return (float64)(length), nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
expString := "strlen('someReallyLongInputString') <= 16"
|
|
||||||
expression, _ := govaluate.NewEvaluableExpressionWithFunctions(expString, functions)
|
|
||||||
|
|
||||||
result, _ := expression.Evaluate(nil)
|
|
||||||
// result is now "false", the boolean value
|
|
||||||
```
|
|
||||||
|
|
||||||
Functions can accept any number of arguments, correctly handles nested functions, and arguments can be of any type (even if none of this library's operators support evaluation of that type). For instance, each of these usages of functions in an expression are valid (assuming that the appropriate functions and parameters are given):
|
|
||||||
|
|
||||||
```go
|
|
||||||
"sqrt(x1 ** y1, x2 ** y2)"
|
|
||||||
"max(someValue, abs(anotherValue), 10 * lastValue)"
|
|
||||||
```
|
|
||||||
|
|
||||||
Functions cannot be passed as parameters, they must be known at the time when the expression is parsed, and are unchangeable after parsing.
|
|
||||||
|
|
||||||
What operators and types does this support?
|
|
||||||
--
|
|
||||||
|
|
||||||
* Modifiers: `+` `-` `/` `*` `&` `|` `^` `**` `%` `>>` `<<`
|
|
||||||
* Comparators: `>` `>=` `<` `<=` `==` `!=` `=~` `!~`
|
|
||||||
* Logical ops: `||` `&&`
|
|
||||||
* Numeric constants, as 64-bit floating point (`12345.678`)
|
|
||||||
* String constants (single quotes: `'foobar'`)
|
|
||||||
* Date constants (single quotes, using any permutation of RFC3339, ISO8601, ruby date, or unix date; date parsing is automatically tried with any string constant)
|
|
||||||
* Boolean constants: `true` `false`
|
|
||||||
* Parenthesis to control order of evaluation `(` `)`
|
|
||||||
* Arrays (anything separated by `,` within parenthesis: `(1, 2, 'foo')`)
|
|
||||||
* Prefixes: `!` `-` `~`
|
|
||||||
* Ternary conditional: `?` `:`
|
|
||||||
* Null coalescence: `??`
|
|
||||||
|
|
||||||
See [MANUAL.md](https://github.com/Knetic/govaluate/blob/master/MANUAL.md) for exacting details on what types each operator supports.
|
|
||||||
|
|
||||||
Types
|
|
||||||
--
|
|
||||||
|
|
||||||
Some operators don't make sense when used with some types. For instance, what does it mean to get the modulo of a string? What happens if you check to see if two numbers are logically AND'ed together?
|
|
||||||
|
|
||||||
Everyone has a different intuition about the answers to these questions. To prevent confusion, this library will _refuse to operate_ upon types for which there is not an unambiguous meaning for the operation. See [MANUAL.md](https://github.com/Knetic/govaluate/blob/master/MANUAL.md) for details about what operators are valid for which types.
|
|
||||||
|
|
||||||
Benchmarks
|
|
||||||
--
|
|
||||||
|
|
||||||
If you're concerned about the overhead of this library, a good range of benchmarks are built into this repo. You can run them with `go test -bench=.`. The library is built with an eye towards being quick, but has not been aggressively profiled and optimized. For most applications, though, it is completely fine.
|
|
||||||
|
|
||||||
For a very rough idea of performance, here are the results output from a benchmark run on a 3rd-gen Macbook Pro (Linux Mint 17.1).
|
|
||||||
|
|
||||||
```
|
|
||||||
BenchmarkSingleParse-12 1000000 1382 ns/op
|
|
||||||
BenchmarkSimpleParse-12 200000 10771 ns/op
|
|
||||||
BenchmarkFullParse-12 30000 49383 ns/op
|
|
||||||
BenchmarkEvaluationSingle-12 50000000 30.1 ns/op
|
|
||||||
BenchmarkEvaluationNumericLiteral-12 10000000 119 ns/op
|
|
||||||
BenchmarkEvaluationLiteralModifiers-12 10000000 236 ns/op
|
|
||||||
BenchmarkEvaluationParameters-12 5000000 260 ns/op
|
|
||||||
BenchmarkEvaluationParametersModifiers-12 3000000 547 ns/op
|
|
||||||
BenchmarkComplexExpression-12 2000000 963 ns/op
|
|
||||||
BenchmarkRegexExpression-12 100000 20357 ns/op
|
|
||||||
BenchmarkConstantRegexExpression-12 1000000 1392 ns/op
|
|
||||||
ok
|
|
||||||
```
|
|
||||||
|
|
||||||
API Breaks
|
|
||||||
--
|
|
||||||
|
|
||||||
While this library has very few cases which will ever result in an API break, it can (and [has](https://github.com/Knetic/govaluate/releases/tag/2.0.0)) happened. If you are using this in production, vendor the commit you've tested against, or use gopkg.in to redirect your import (e.g., `import "gopkg.in/Knetic/govaluate.v2"`). Master branch (while infrequent) _may_ at some point contain API breaking changes, and the author will have no way to communicate these to downstreams, other than creating a new major release.
|
|
||||||
|
|
||||||
Releases will explicitly state when an API break happens, and if they do not specify an API break it should be safe to upgrade.
|
|
||||||
|
|
||||||
License
|
|
||||||
--
|
|
||||||
|
|
||||||
This project is licensed under the MIT general use license. You're free to integrate, fork, and play with this code as you feel fit without consulting the author, as long as you provide proper credit to the author in your works.
|
|
72
vendor/github.com/Knetic/govaluate/TokenKind.go
generated
vendored
72
vendor/github.com/Knetic/govaluate/TokenKind.go
generated
vendored
@ -1,72 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
/*
|
|
||||||
Represents all valid types of tokens that a token can be.
|
|
||||||
*/
|
|
||||||
type TokenKind int
|
|
||||||
|
|
||||||
const (
|
|
||||||
UNKNOWN TokenKind = iota
|
|
||||||
|
|
||||||
PREFIX
|
|
||||||
NUMERIC
|
|
||||||
BOOLEAN
|
|
||||||
STRING
|
|
||||||
PATTERN
|
|
||||||
TIME
|
|
||||||
VARIABLE
|
|
||||||
FUNCTION
|
|
||||||
SEPARATOR
|
|
||||||
|
|
||||||
COMPARATOR
|
|
||||||
LOGICALOP
|
|
||||||
MODIFIER
|
|
||||||
|
|
||||||
CLAUSE
|
|
||||||
CLAUSE_CLOSE
|
|
||||||
|
|
||||||
TERNARY
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
GetTokenKindString returns a string that describes the given TokenKind.
|
|
||||||
e.g., when passed the NUMERIC TokenKind, this returns the string "NUMERIC".
|
|
||||||
*/
|
|
||||||
func (kind TokenKind) String() string {
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
|
|
||||||
case PREFIX:
|
|
||||||
return "PREFIX"
|
|
||||||
case NUMERIC:
|
|
||||||
return "NUMERIC"
|
|
||||||
case BOOLEAN:
|
|
||||||
return "BOOLEAN"
|
|
||||||
case STRING:
|
|
||||||
return "STRING"
|
|
||||||
case PATTERN:
|
|
||||||
return "PATTERN"
|
|
||||||
case TIME:
|
|
||||||
return "TIME"
|
|
||||||
case VARIABLE:
|
|
||||||
return "VARIABLE"
|
|
||||||
case FUNCTION:
|
|
||||||
return "FUNCTION"
|
|
||||||
case SEPARATOR:
|
|
||||||
return "SEPARATOR"
|
|
||||||
case COMPARATOR:
|
|
||||||
return "COMPARATOR"
|
|
||||||
case LOGICALOP:
|
|
||||||
return "LOGICALOP"
|
|
||||||
case MODIFIER:
|
|
||||||
return "MODIFIER"
|
|
||||||
case CLAUSE:
|
|
||||||
return "CLAUSE"
|
|
||||||
case CLAUSE_CLOSE:
|
|
||||||
return "CLAUSE_CLOSE"
|
|
||||||
case TERNARY:
|
|
||||||
return "TERNARY"
|
|
||||||
}
|
|
||||||
|
|
||||||
return "UNKNOWN"
|
|
||||||
}
|
|
359
vendor/github.com/Knetic/govaluate/evaluationStage.go
generated
vendored
359
vendor/github.com/Knetic/govaluate/evaluationStage.go
generated
vendored
@ -1,359 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"regexp"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
logicalErrorFormat string = "Value '%v' cannot be used with the logical operator '%v', it is not a bool"
|
|
||||||
modifierErrorFormat string = "Value '%v' cannot be used with the modifier '%v', it is not a number"
|
|
||||||
comparatorErrorFormat string = "Value '%v' cannot be used with the comparator '%v', it is not a number"
|
|
||||||
ternaryErrorFormat string = "Value '%v' cannot be used with the ternary operator '%v', it is not a bool"
|
|
||||||
prefixErrorFormat string = "Value '%v' cannot be used with the prefix '%v'"
|
|
||||||
)
|
|
||||||
|
|
||||||
type evaluationOperator func(left interface{}, right interface{}, parameters Parameters) (interface{}, error)
|
|
||||||
type stageTypeCheck func(value interface{}) bool
|
|
||||||
type stageCombinedTypeCheck func(left interface{}, right interface{}) bool
|
|
||||||
|
|
||||||
type evaluationStage struct {
|
|
||||||
symbol OperatorSymbol
|
|
||||||
|
|
||||||
leftStage, rightStage *evaluationStage
|
|
||||||
|
|
||||||
// the operation that will be used to evaluate this stage (such as adding [left] to [right] and return the result)
|
|
||||||
operator evaluationOperator
|
|
||||||
|
|
||||||
// ensures that both left and right values are appropriate for this stage. Returns an error if they aren't operable.
|
|
||||||
leftTypeCheck stageTypeCheck
|
|
||||||
rightTypeCheck stageTypeCheck
|
|
||||||
|
|
||||||
// if specified, will override whatever is used in "leftTypeCheck" and "rightTypeCheck".
|
|
||||||
// primarily used for specific operators that don't care which side a given type is on, but still requires one side to be of a given type
|
|
||||||
// (like string concat)
|
|
||||||
typeCheck stageCombinedTypeCheck
|
|
||||||
|
|
||||||
// regardless of which type check is used, this string format will be used as the error message for type errors
|
|
||||||
typeErrorFormat string
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_true = interface{}(true)
|
|
||||||
_false = interface{}(false)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (this *evaluationStage) swapWith(other *evaluationStage) {
|
|
||||||
|
|
||||||
temp := *other
|
|
||||||
other.setToNonStage(*this)
|
|
||||||
this.setToNonStage(temp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *evaluationStage) setToNonStage(other evaluationStage) {
|
|
||||||
|
|
||||||
this.symbol = other.symbol
|
|
||||||
this.operator = other.operator
|
|
||||||
this.leftTypeCheck = other.leftTypeCheck
|
|
||||||
this.rightTypeCheck = other.rightTypeCheck
|
|
||||||
this.typeCheck = other.typeCheck
|
|
||||||
this.typeErrorFormat = other.typeErrorFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *evaluationStage) isShortCircuitable() bool {
|
|
||||||
|
|
||||||
switch this.symbol {
|
|
||||||
case AND:
|
|
||||||
fallthrough
|
|
||||||
case OR:
|
|
||||||
fallthrough
|
|
||||||
case TERNARY_TRUE:
|
|
||||||
fallthrough
|
|
||||||
case TERNARY_FALSE:
|
|
||||||
fallthrough
|
|
||||||
case COALESCE:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func noopStageRight(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return right, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
|
|
||||||
// string concat if either are strings
|
|
||||||
if isString(left) || isString(right) {
|
|
||||||
return fmt.Sprintf("%v%v", left, right), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return left.(float64) + right.(float64), nil
|
|
||||||
}
|
|
||||||
func subtractStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return left.(float64) - right.(float64), nil
|
|
||||||
}
|
|
||||||
func multiplyStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return left.(float64) * right.(float64), nil
|
|
||||||
}
|
|
||||||
func divideStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return left.(float64) / right.(float64), nil
|
|
||||||
}
|
|
||||||
func exponentStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return math.Pow(left.(float64), right.(float64)), nil
|
|
||||||
}
|
|
||||||
func modulusStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return math.Mod(left.(float64), right.(float64)), nil
|
|
||||||
}
|
|
||||||
func gteStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
if isString(left) && isString(right) {
|
|
||||||
return boolIface(left.(string) >= right.(string)), nil
|
|
||||||
}
|
|
||||||
return boolIface(left.(float64) >= right.(float64)), nil
|
|
||||||
}
|
|
||||||
func gtStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
if isString(left) && isString(right) {
|
|
||||||
return boolIface(left.(string) > right.(string)), nil
|
|
||||||
}
|
|
||||||
return boolIface(left.(float64) > right.(float64)), nil
|
|
||||||
}
|
|
||||||
func lteStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
if isString(left) && isString(right) {
|
|
||||||
return boolIface(left.(string) <= right.(string)), nil
|
|
||||||
}
|
|
||||||
return boolIface(left.(float64) <= right.(float64)), nil
|
|
||||||
}
|
|
||||||
func ltStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
if isString(left) && isString(right) {
|
|
||||||
return boolIface(left.(string) < right.(string)), nil
|
|
||||||
}
|
|
||||||
return boolIface(left.(float64) < right.(float64)), nil
|
|
||||||
}
|
|
||||||
func equalStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return boolIface(reflect.DeepEqual(left, right)), nil
|
|
||||||
}
|
|
||||||
func notEqualStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return boolIface(!reflect.DeepEqual(left, right)), nil
|
|
||||||
}
|
|
||||||
func andStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return boolIface(left.(bool) && right.(bool)), nil
|
|
||||||
}
|
|
||||||
func orStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return boolIface(left.(bool) || right.(bool)), nil
|
|
||||||
}
|
|
||||||
func negateStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return -right.(float64), nil
|
|
||||||
}
|
|
||||||
func invertStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return boolIface(!right.(bool)), nil
|
|
||||||
}
|
|
||||||
func bitwiseNotStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return float64(^int64(right.(float64))), nil
|
|
||||||
}
|
|
||||||
func ternaryIfStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
if left.(bool) {
|
|
||||||
return right, nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
func ternaryElseStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
if left != nil {
|
|
||||||
return left, nil
|
|
||||||
}
|
|
||||||
return right, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func regexStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
|
|
||||||
var pattern *regexp.Regexp
|
|
||||||
var err error
|
|
||||||
|
|
||||||
switch right.(type) {
|
|
||||||
case string:
|
|
||||||
pattern, err = regexp.Compile(right.(string))
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New(fmt.Sprintf("Unable to compile regexp pattern '%v': %v", right, err))
|
|
||||||
}
|
|
||||||
case *regexp.Regexp:
|
|
||||||
pattern = right.(*regexp.Regexp)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pattern.Match([]byte(left.(string))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func notRegexStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
|
|
||||||
ret, err := regexStage(left, right, parameters)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return !(ret.(bool)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func bitwiseOrStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return float64(int64(left.(float64)) | int64(right.(float64))), nil
|
|
||||||
}
|
|
||||||
func bitwiseAndStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return float64(int64(left.(float64)) & int64(right.(float64))), nil
|
|
||||||
}
|
|
||||||
func bitwiseXORStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return float64(int64(left.(float64)) ^ int64(right.(float64))), nil
|
|
||||||
}
|
|
||||||
func leftShiftStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return float64(uint64(left.(float64)) << uint64(right.(float64))), nil
|
|
||||||
}
|
|
||||||
func rightShiftStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return float64(uint64(left.(float64)) >> uint64(right.(float64))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeParameterStage(parameterName string) evaluationOperator {
|
|
||||||
|
|
||||||
return func(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
value, err := parameters.Get(parameterName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeLiteralStage(literal interface{}) evaluationOperator {
|
|
||||||
return func(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
return literal, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeFunctionStage(function ExpressionFunction) evaluationOperator {
|
|
||||||
|
|
||||||
return func(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
|
|
||||||
if right == nil {
|
|
||||||
return function()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch right.(type) {
|
|
||||||
case []interface{}:
|
|
||||||
return function(right.([]interface{})...)
|
|
||||||
default:
|
|
||||||
return function(right)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func separatorStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
|
|
||||||
var ret []interface{}
|
|
||||||
|
|
||||||
switch left.(type) {
|
|
||||||
case []interface{}:
|
|
||||||
ret = append(left.([]interface{}), right)
|
|
||||||
default:
|
|
||||||
ret = []interface{}{left, right}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func inStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {
|
|
||||||
|
|
||||||
for _, value := range right.([]interface{}) {
|
|
||||||
if left == value {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
func isString(value interface{}) bool {
|
|
||||||
|
|
||||||
switch value.(type) {
|
|
||||||
case string:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isRegexOrString(value interface{}) bool {
|
|
||||||
|
|
||||||
switch value.(type) {
|
|
||||||
case string:
|
|
||||||
return true
|
|
||||||
case *regexp.Regexp:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBool(value interface{}) bool {
|
|
||||||
switch value.(type) {
|
|
||||||
case bool:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isFloat64(value interface{}) bool {
|
|
||||||
switch value.(type) {
|
|
||||||
case float64:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Addition usually means between numbers, but can also mean string concat.
|
|
||||||
String concat needs one (or both) of the sides to be a string.
|
|
||||||
*/
|
|
||||||
func additionTypeCheck(left interface{}, right interface{}) bool {
|
|
||||||
|
|
||||||
if isFloat64(left) && isFloat64(right) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if !isString(left) && !isString(right) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Comparison can either be between numbers, or lexicographic between two strings,
|
|
||||||
but never between the two.
|
|
||||||
*/
|
|
||||||
func comparatorTypeCheck(left interface{}, right interface{}) bool {
|
|
||||||
|
|
||||||
if isFloat64(left) && isFloat64(right) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if isString(left) && isString(right) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isArray(value interface{}) bool {
|
|
||||||
switch value.(type) {
|
|
||||||
case []interface{}:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Converting a boolean to an interface{} requires an allocation.
|
|
||||||
We can use interned bools to avoid this cost.
|
|
||||||
*/
|
|
||||||
func boolIface(b bool) interface{} {
|
|
||||||
if b {
|
|
||||||
return _true
|
|
||||||
}
|
|
||||||
return _false
|
|
||||||
}
|
|
8
vendor/github.com/Knetic/govaluate/expressionFunctions.go
generated
vendored
8
vendor/github.com/Knetic/govaluate/expressionFunctions.go
generated
vendored
@ -1,8 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
/*
|
|
||||||
Represents a function that can be called from within an expression.
|
|
||||||
This method must return an error if, for any reason, it is unable to produce exactly one unambiguous result.
|
|
||||||
An error returned will halt execution of the expression.
|
|
||||||
*/
|
|
||||||
type ExpressionFunction func(arguments ...interface{}) (interface{}, error)
|
|
46
vendor/github.com/Knetic/govaluate/expressionOutputStream.go
generated
vendored
46
vendor/github.com/Knetic/govaluate/expressionOutputStream.go
generated
vendored
@ -1,46 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Holds a series of "transactions" which represent each token as it is output by an outputter (such as ToSQLQuery()).
|
|
||||||
Some outputs (such as SQL) require a function call or non-c-like syntax to represent an expression.
|
|
||||||
To accomplish this, this struct keeps track of each translated token as it is output, and can return and rollback those transactions.
|
|
||||||
*/
|
|
||||||
type expressionOutputStream struct {
|
|
||||||
transactions []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *expressionOutputStream) add(transaction string) {
|
|
||||||
this.transactions = append(this.transactions, transaction)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *expressionOutputStream) rollback() string {
|
|
||||||
|
|
||||||
index := len(this.transactions) - 1
|
|
||||||
ret := this.transactions[index]
|
|
||||||
|
|
||||||
this.transactions = this.transactions[:index]
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *expressionOutputStream) createString(delimiter string) string {
|
|
||||||
|
|
||||||
var retBuffer bytes.Buffer
|
|
||||||
var transaction string
|
|
||||||
|
|
||||||
penultimate := len(this.transactions) - 1
|
|
||||||
|
|
||||||
for i := 0; i < penultimate; i++ {
|
|
||||||
|
|
||||||
transaction = this.transactions[i]
|
|
||||||
|
|
||||||
retBuffer.WriteString(transaction)
|
|
||||||
retBuffer.WriteString(delimiter)
|
|
||||||
}
|
|
||||||
retBuffer.WriteString(this.transactions[penultimate])
|
|
||||||
|
|
||||||
return retBuffer.String()
|
|
||||||
}
|
|
350
vendor/github.com/Knetic/govaluate/lexerState.go
generated
vendored
350
vendor/github.com/Knetic/govaluate/lexerState.go
generated
vendored
@ -1,350 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type lexerState struct {
|
|
||||||
isEOF bool
|
|
||||||
isNullable bool
|
|
||||||
kind TokenKind
|
|
||||||
validNextKinds []TokenKind
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexer states.
|
|
||||||
// Constant for all purposes except compiler.
|
|
||||||
var validLexerStates = []lexerState{
|
|
||||||
|
|
||||||
lexerState{
|
|
||||||
kind: UNKNOWN,
|
|
||||||
isEOF: false,
|
|
||||||
isNullable: true,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
PREFIX,
|
|
||||||
NUMERIC,
|
|
||||||
BOOLEAN,
|
|
||||||
VARIABLE,
|
|
||||||
PATTERN,
|
|
||||||
FUNCTION,
|
|
||||||
STRING,
|
|
||||||
TIME,
|
|
||||||
CLAUSE,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: CLAUSE,
|
|
||||||
isEOF: false,
|
|
||||||
isNullable: true,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
PREFIX,
|
|
||||||
NUMERIC,
|
|
||||||
BOOLEAN,
|
|
||||||
VARIABLE,
|
|
||||||
PATTERN,
|
|
||||||
FUNCTION,
|
|
||||||
STRING,
|
|
||||||
TIME,
|
|
||||||
CLAUSE,
|
|
||||||
CLAUSE_CLOSE,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: CLAUSE_CLOSE,
|
|
||||||
isEOF: true,
|
|
||||||
isNullable: true,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
COMPARATOR,
|
|
||||||
MODIFIER,
|
|
||||||
NUMERIC,
|
|
||||||
BOOLEAN,
|
|
||||||
VARIABLE,
|
|
||||||
STRING,
|
|
||||||
PATTERN,
|
|
||||||
TIME,
|
|
||||||
CLAUSE,
|
|
||||||
CLAUSE_CLOSE,
|
|
||||||
LOGICALOP,
|
|
||||||
TERNARY,
|
|
||||||
SEPARATOR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: NUMERIC,
|
|
||||||
isEOF: true,
|
|
||||||
isNullable: false,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
MODIFIER,
|
|
||||||
COMPARATOR,
|
|
||||||
LOGICALOP,
|
|
||||||
CLAUSE_CLOSE,
|
|
||||||
TERNARY,
|
|
||||||
SEPARATOR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: BOOLEAN,
|
|
||||||
isEOF: true,
|
|
||||||
isNullable: false,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
MODIFIER,
|
|
||||||
COMPARATOR,
|
|
||||||
LOGICALOP,
|
|
||||||
CLAUSE_CLOSE,
|
|
||||||
TERNARY,
|
|
||||||
SEPARATOR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: STRING,
|
|
||||||
isEOF: true,
|
|
||||||
isNullable: false,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
MODIFIER,
|
|
||||||
COMPARATOR,
|
|
||||||
LOGICALOP,
|
|
||||||
CLAUSE_CLOSE,
|
|
||||||
TERNARY,
|
|
||||||
SEPARATOR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: TIME,
|
|
||||||
isEOF: true,
|
|
||||||
isNullable: false,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
MODIFIER,
|
|
||||||
COMPARATOR,
|
|
||||||
LOGICALOP,
|
|
||||||
CLAUSE_CLOSE,
|
|
||||||
SEPARATOR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: PATTERN,
|
|
||||||
isEOF: true,
|
|
||||||
isNullable: false,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
MODIFIER,
|
|
||||||
COMPARATOR,
|
|
||||||
LOGICALOP,
|
|
||||||
CLAUSE_CLOSE,
|
|
||||||
SEPARATOR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: VARIABLE,
|
|
||||||
isEOF: true,
|
|
||||||
isNullable: false,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
MODIFIER,
|
|
||||||
COMPARATOR,
|
|
||||||
LOGICALOP,
|
|
||||||
CLAUSE_CLOSE,
|
|
||||||
TERNARY,
|
|
||||||
SEPARATOR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: MODIFIER,
|
|
||||||
isEOF: false,
|
|
||||||
isNullable: false,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
PREFIX,
|
|
||||||
NUMERIC,
|
|
||||||
VARIABLE,
|
|
||||||
FUNCTION,
|
|
||||||
STRING,
|
|
||||||
BOOLEAN,
|
|
||||||
CLAUSE,
|
|
||||||
CLAUSE_CLOSE,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: COMPARATOR,
|
|
||||||
isEOF: false,
|
|
||||||
isNullable: false,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
PREFIX,
|
|
||||||
NUMERIC,
|
|
||||||
BOOLEAN,
|
|
||||||
VARIABLE,
|
|
||||||
FUNCTION,
|
|
||||||
STRING,
|
|
||||||
TIME,
|
|
||||||
CLAUSE,
|
|
||||||
CLAUSE_CLOSE,
|
|
||||||
PATTERN,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: LOGICALOP,
|
|
||||||
isEOF: false,
|
|
||||||
isNullable: false,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
PREFIX,
|
|
||||||
NUMERIC,
|
|
||||||
BOOLEAN,
|
|
||||||
VARIABLE,
|
|
||||||
FUNCTION,
|
|
||||||
STRING,
|
|
||||||
TIME,
|
|
||||||
CLAUSE,
|
|
||||||
CLAUSE_CLOSE,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: PREFIX,
|
|
||||||
isEOF: false,
|
|
||||||
isNullable: false,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
NUMERIC,
|
|
||||||
BOOLEAN,
|
|
||||||
VARIABLE,
|
|
||||||
FUNCTION,
|
|
||||||
CLAUSE,
|
|
||||||
CLAUSE_CLOSE,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: TERNARY,
|
|
||||||
isEOF: false,
|
|
||||||
isNullable: false,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
PREFIX,
|
|
||||||
NUMERIC,
|
|
||||||
BOOLEAN,
|
|
||||||
STRING,
|
|
||||||
TIME,
|
|
||||||
VARIABLE,
|
|
||||||
FUNCTION,
|
|
||||||
CLAUSE,
|
|
||||||
SEPARATOR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: FUNCTION,
|
|
||||||
isEOF: false,
|
|
||||||
isNullable: false,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
CLAUSE,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lexerState{
|
|
||||||
|
|
||||||
kind: SEPARATOR,
|
|
||||||
isEOF: false,
|
|
||||||
isNullable: true,
|
|
||||||
validNextKinds: []TokenKind{
|
|
||||||
|
|
||||||
PREFIX,
|
|
||||||
NUMERIC,
|
|
||||||
BOOLEAN,
|
|
||||||
STRING,
|
|
||||||
TIME,
|
|
||||||
VARIABLE,
|
|
||||||
FUNCTION,
|
|
||||||
CLAUSE,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this lexerState) canTransitionTo(kind TokenKind) bool {
|
|
||||||
|
|
||||||
for _, validKind := range this.validNextKinds {
|
|
||||||
|
|
||||||
if validKind == kind {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkExpressionSyntax(tokens []ExpressionToken) error {
|
|
||||||
|
|
||||||
var state lexerState
|
|
||||||
var lastToken ExpressionToken
|
|
||||||
var err error
|
|
||||||
|
|
||||||
state = validLexerStates[0]
|
|
||||||
|
|
||||||
for _, token := range tokens {
|
|
||||||
|
|
||||||
if !state.canTransitionTo(token.Kind) {
|
|
||||||
|
|
||||||
// call out a specific error for tokens looking like they want to be functions.
|
|
||||||
if lastToken.Kind == VARIABLE && token.Kind == CLAUSE {
|
|
||||||
return errors.New("Undefined function " + lastToken.Value.(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
firstStateName := fmt.Sprintf("%s [%v]", state.kind.String(), lastToken.Value)
|
|
||||||
nextStateName := fmt.Sprintf("%s [%v]", token.Kind.String(), token.Value)
|
|
||||||
|
|
||||||
return errors.New("Cannot transition token types from " + firstStateName + " to " + nextStateName)
|
|
||||||
}
|
|
||||||
|
|
||||||
state, err = getLexerStateForToken(token.Kind)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !state.isNullable && token.Value == nil {
|
|
||||||
|
|
||||||
errorMsg := fmt.Sprintf("Token kind '%v' cannot have a nil value", token.Kind.String())
|
|
||||||
return errors.New(errorMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
lastToken = token
|
|
||||||
}
|
|
||||||
|
|
||||||
if !state.isEOF {
|
|
||||||
return errors.New("Unexpected end of expression")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLexerStateForToken(kind TokenKind) (lexerState, error) {
|
|
||||||
|
|
||||||
for _, possibleState := range validLexerStates {
|
|
||||||
|
|
||||||
if possibleState.kind == kind {
|
|
||||||
return possibleState, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errorMsg := fmt.Sprintf("No lexer state found for token kind '%v'\n", kind.String())
|
|
||||||
return validLexerStates[0], errors.New(errorMsg)
|
|
||||||
}
|
|
39
vendor/github.com/Knetic/govaluate/lexerStream.go
generated
vendored
39
vendor/github.com/Knetic/govaluate/lexerStream.go
generated
vendored
@ -1,39 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
type lexerStream struct {
|
|
||||||
source []rune
|
|
||||||
position int
|
|
||||||
length int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newLexerStream(source string) *lexerStream {
|
|
||||||
|
|
||||||
var ret *lexerStream
|
|
||||||
var runes []rune
|
|
||||||
|
|
||||||
for _, character := range source {
|
|
||||||
runes = append(runes, character)
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = new(lexerStream)
|
|
||||||
ret.source = runes
|
|
||||||
ret.length = len(runes)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *lexerStream) readCharacter() rune {
|
|
||||||
|
|
||||||
var character rune
|
|
||||||
|
|
||||||
character = this.source[this.position]
|
|
||||||
this.position += 1
|
|
||||||
return character
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *lexerStream) rewind(amount int) {
|
|
||||||
this.position -= amount
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this lexerStream) canRead() bool {
|
|
||||||
return this.position < this.length
|
|
||||||
}
|
|
32
vendor/github.com/Knetic/govaluate/parameters.go
generated
vendored
32
vendor/github.com/Knetic/govaluate/parameters.go
generated
vendored
@ -1,32 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Parameters is a collection of named parameters that can be used by an EvaluableExpression to retrieve parameters
|
|
||||||
when an expression tries to use them.
|
|
||||||
*/
|
|
||||||
type Parameters interface {
|
|
||||||
|
|
||||||
/*
|
|
||||||
Get gets the parameter of the given name, or an error if the parameter is unavailable.
|
|
||||||
Failure to find the given parameter should be indicated by returning an error.
|
|
||||||
*/
|
|
||||||
Get(name string) (interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MapParameters map[string]interface{}
|
|
||||||
|
|
||||||
func (p MapParameters) Get(name string) (interface{}, error) {
|
|
||||||
|
|
||||||
value, found := p[name]
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
errorMessage := "No parameter '" + name + "' found."
|
|
||||||
return nil, errors.New(errorMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
return value, nil
|
|
||||||
}
|
|
450
vendor/github.com/Knetic/govaluate/parsing.go
generated
vendored
450
vendor/github.com/Knetic/govaluate/parsing.go
generated
vendored
@ -1,450 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseTokens(expression string, functions map[string]ExpressionFunction) ([]ExpressionToken, error) {
|
|
||||||
|
|
||||||
var ret []ExpressionToken
|
|
||||||
var token ExpressionToken
|
|
||||||
var stream *lexerStream
|
|
||||||
var state lexerState
|
|
||||||
var err error
|
|
||||||
var found bool
|
|
||||||
|
|
||||||
stream = newLexerStream(expression)
|
|
||||||
state = validLexerStates[0]
|
|
||||||
|
|
||||||
for stream.canRead() {
|
|
||||||
|
|
||||||
token, err, found = readToken(stream, state, functions)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
state, err = getLexerStateForToken(token.Kind)
|
|
||||||
if err != nil {
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// append this valid token
|
|
||||||
ret = append(ret, token)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = checkBalance(ret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readToken(stream *lexerStream, state lexerState, functions map[string]ExpressionFunction) (ExpressionToken, error, bool) {
|
|
||||||
|
|
||||||
var function ExpressionFunction
|
|
||||||
var ret ExpressionToken
|
|
||||||
var tokenValue interface{}
|
|
||||||
var tokenTime time.Time
|
|
||||||
var tokenString string
|
|
||||||
var kind TokenKind
|
|
||||||
var character rune
|
|
||||||
var found bool
|
|
||||||
var completed bool
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// numeric is 0-9, or .
|
|
||||||
// string starts with '
|
|
||||||
// variable is alphanumeric, always starts with a letter
|
|
||||||
// bracket always means variable
|
|
||||||
// symbols are anything non-alphanumeric
|
|
||||||
// all others read into a buffer until they reach the end of the stream
|
|
||||||
for stream.canRead() {
|
|
||||||
|
|
||||||
character = stream.readCharacter()
|
|
||||||
|
|
||||||
if unicode.IsSpace(character) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
kind = UNKNOWN
|
|
||||||
|
|
||||||
// numeric constant
|
|
||||||
if isNumeric(character) {
|
|
||||||
|
|
||||||
tokenString = readTokenUntilFalse(stream, isNumeric)
|
|
||||||
tokenValue, err = strconv.ParseFloat(tokenString, 64)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errorMsg := fmt.Sprintf("Unable to parse numeric value '%v' to float64\n", tokenString)
|
|
||||||
return ExpressionToken{}, errors.New(errorMsg), false
|
|
||||||
}
|
|
||||||
kind = NUMERIC
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// comma, separator
|
|
||||||
if character == ',' {
|
|
||||||
|
|
||||||
tokenValue = ","
|
|
||||||
kind = SEPARATOR
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// escaped variable
|
|
||||||
if character == '[' {
|
|
||||||
|
|
||||||
tokenValue, completed = readUntilFalse(stream, true, false, true, isNotClosingBracket)
|
|
||||||
kind = VARIABLE
|
|
||||||
|
|
||||||
if !completed {
|
|
||||||
return ExpressionToken{}, errors.New("Unclosed parameter bracket"), false
|
|
||||||
}
|
|
||||||
|
|
||||||
// above method normally rewinds us to the closing bracket, which we want to skip.
|
|
||||||
stream.rewind(-1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// regular variable - or function?
|
|
||||||
if unicode.IsLetter(character) {
|
|
||||||
|
|
||||||
tokenString = readTokenUntilFalse(stream, isVariableName)
|
|
||||||
|
|
||||||
tokenValue = tokenString
|
|
||||||
kind = VARIABLE
|
|
||||||
|
|
||||||
// boolean?
|
|
||||||
if tokenValue == "true" {
|
|
||||||
|
|
||||||
kind = BOOLEAN
|
|
||||||
tokenValue = true
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if tokenValue == "false" {
|
|
||||||
|
|
||||||
kind = BOOLEAN
|
|
||||||
tokenValue = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// textual operator?
|
|
||||||
if tokenValue == "in" || tokenValue == "IN" {
|
|
||||||
|
|
||||||
// force lower case for consistency
|
|
||||||
tokenValue = "in"
|
|
||||||
kind = COMPARATOR
|
|
||||||
}
|
|
||||||
|
|
||||||
// function?
|
|
||||||
function, found = functions[tokenString]
|
|
||||||
if found {
|
|
||||||
kind = FUNCTION
|
|
||||||
tokenValue = function
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isNotQuote(character) {
|
|
||||||
tokenValue, completed = readUntilFalse(stream, true, false, true, isNotQuote)
|
|
||||||
|
|
||||||
if !completed {
|
|
||||||
return ExpressionToken{}, errors.New("Unclosed string literal"), false
|
|
||||||
}
|
|
||||||
|
|
||||||
// advance the stream one position, since reading until false assumes the terminator is a real token
|
|
||||||
stream.rewind(-1)
|
|
||||||
|
|
||||||
// check to see if this can be parsed as a time.
|
|
||||||
tokenTime, found = tryParseTime(tokenValue.(string))
|
|
||||||
if found {
|
|
||||||
kind = TIME
|
|
||||||
tokenValue = tokenTime
|
|
||||||
} else {
|
|
||||||
kind = STRING
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if character == '(' {
|
|
||||||
tokenValue = character
|
|
||||||
kind = CLAUSE
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if character == ')' {
|
|
||||||
tokenValue = character
|
|
||||||
kind = CLAUSE_CLOSE
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// must be a known symbol
|
|
||||||
tokenString = readTokenUntilFalse(stream, isNotAlphanumeric)
|
|
||||||
tokenValue = tokenString
|
|
||||||
|
|
||||||
// quick hack for the case where "-" can mean "prefixed negation" or "minus", which are used
|
|
||||||
// very differently.
|
|
||||||
if state.canTransitionTo(PREFIX) {
|
|
||||||
_, found = prefixSymbols[tokenString]
|
|
||||||
if found {
|
|
||||||
|
|
||||||
kind = PREFIX
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, found = modifierSymbols[tokenString]
|
|
||||||
if found {
|
|
||||||
|
|
||||||
kind = MODIFIER
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
_, found = logicalSymbols[tokenString]
|
|
||||||
if found {
|
|
||||||
|
|
||||||
kind = LOGICALOP
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
_, found = comparatorSymbols[tokenString]
|
|
||||||
if found {
|
|
||||||
|
|
||||||
kind = COMPARATOR
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
_, found = ternarySymbols[tokenString]
|
|
||||||
if found {
|
|
||||||
|
|
||||||
kind = TERNARY
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
errorMessage := fmt.Sprintf("Invalid token: '%s'", tokenString)
|
|
||||||
return ret, errors.New(errorMessage), false
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Kind = kind
|
|
||||||
ret.Value = tokenValue
|
|
||||||
|
|
||||||
return ret, nil, (kind != UNKNOWN)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readTokenUntilFalse(stream *lexerStream, condition func(rune) bool) string {
|
|
||||||
|
|
||||||
var ret string
|
|
||||||
|
|
||||||
stream.rewind(1)
|
|
||||||
ret, _ = readUntilFalse(stream, false, true, true, condition)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Returns the string that was read until the given [condition] was false, or whitespace was broken.
|
|
||||||
Returns false if the stream ended before whitespace was broken or condition was met.
|
|
||||||
*/
|
|
||||||
func readUntilFalse(stream *lexerStream, includeWhitespace bool, breakWhitespace bool, allowEscaping bool, condition func(rune) bool) (string, bool) {
|
|
||||||
|
|
||||||
var tokenBuffer bytes.Buffer
|
|
||||||
var character rune
|
|
||||||
var conditioned bool
|
|
||||||
|
|
||||||
conditioned = false
|
|
||||||
|
|
||||||
for stream.canRead() {
|
|
||||||
|
|
||||||
character = stream.readCharacter()
|
|
||||||
|
|
||||||
// Use backslashes to escape anything
|
|
||||||
if allowEscaping && character == '\\' {
|
|
||||||
|
|
||||||
character = stream.readCharacter()
|
|
||||||
tokenBuffer.WriteString(string(character))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if unicode.IsSpace(character) {
|
|
||||||
|
|
||||||
if breakWhitespace && tokenBuffer.Len() > 0 {
|
|
||||||
conditioned = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !includeWhitespace {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if condition(character) {
|
|
||||||
tokenBuffer.WriteString(string(character))
|
|
||||||
} else {
|
|
||||||
conditioned = true
|
|
||||||
stream.rewind(1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokenBuffer.String(), conditioned
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Checks to see if any optimizations can be performed on the given [tokens], which form a complete, valid expression.
|
|
||||||
The returns slice will represent the optimized (or unmodified) list of tokens to use.
|
|
||||||
*/
|
|
||||||
func optimizeTokens(tokens []ExpressionToken) ([]ExpressionToken, error) {
|
|
||||||
|
|
||||||
var token ExpressionToken
|
|
||||||
var symbol OperatorSymbol
|
|
||||||
var err error
|
|
||||||
var index int
|
|
||||||
|
|
||||||
for index, token = range tokens {
|
|
||||||
|
|
||||||
// if we find a regex operator, and the right-hand value is a constant, precompile and replace with a pattern.
|
|
||||||
if token.Kind != COMPARATOR {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
symbol = comparatorSymbols[token.Value.(string)]
|
|
||||||
if symbol != REQ && symbol != NREQ {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
index++
|
|
||||||
token = tokens[index]
|
|
||||||
if token.Kind == STRING {
|
|
||||||
|
|
||||||
token.Kind = PATTERN
|
|
||||||
token.Value, err = regexp.Compile(token.Value.(string))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return tokens, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens[index] = token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tokens, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Checks the balance of tokens which have multiple parts, such as parenthesis.
|
|
||||||
*/
|
|
||||||
func checkBalance(tokens []ExpressionToken) error {
|
|
||||||
|
|
||||||
var stream *tokenStream
|
|
||||||
var token ExpressionToken
|
|
||||||
var parens int
|
|
||||||
|
|
||||||
stream = newTokenStream(tokens)
|
|
||||||
|
|
||||||
for stream.hasNext() {
|
|
||||||
|
|
||||||
token = stream.next()
|
|
||||||
if token.Kind == CLAUSE {
|
|
||||||
parens++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if token.Kind == CLAUSE_CLOSE {
|
|
||||||
parens--
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if parens != 0 {
|
|
||||||
return errors.New("Unbalanced parenthesis")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNumeric(character rune) bool {
|
|
||||||
|
|
||||||
return unicode.IsDigit(character) || character == '.'
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNotQuote(character rune) bool {
|
|
||||||
|
|
||||||
return character != '\'' && character != '"'
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNotAlphanumeric(character rune) bool {
|
|
||||||
|
|
||||||
return !(unicode.IsDigit(character) ||
|
|
||||||
unicode.IsLetter(character) ||
|
|
||||||
character == '(' ||
|
|
||||||
character == ')' ||
|
|
||||||
!isNotQuote(character))
|
|
||||||
}
|
|
||||||
|
|
||||||
func isVariableName(character rune) bool {
|
|
||||||
|
|
||||||
return unicode.IsLetter(character) ||
|
|
||||||
unicode.IsDigit(character) ||
|
|
||||||
character == '_'
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNotClosingBracket(character rune) bool {
|
|
||||||
|
|
||||||
return character != ']'
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Attempts to parse the [candidate] as a Time.
|
|
||||||
Tries a series of standardized date formats, returns the Time if one applies,
|
|
||||||
otherwise returns false through the second return.
|
|
||||||
*/
|
|
||||||
func tryParseTime(candidate string) (time.Time, bool) {
|
|
||||||
|
|
||||||
var ret time.Time
|
|
||||||
var found bool
|
|
||||||
|
|
||||||
timeFormats := [...]string{
|
|
||||||
time.ANSIC,
|
|
||||||
time.UnixDate,
|
|
||||||
time.RubyDate,
|
|
||||||
time.Kitchen,
|
|
||||||
time.RFC3339,
|
|
||||||
time.RFC3339Nano,
|
|
||||||
"2006-01-02", // RFC 3339
|
|
||||||
"2006-01-02 15:04", // RFC 3339 with minutes
|
|
||||||
"2006-01-02 15:04:05", // RFC 3339 with seconds
|
|
||||||
"2006-01-02 15:04:05-07:00", // RFC 3339 with seconds and timezone
|
|
||||||
"2006-01-02T15Z0700", // ISO8601 with hour
|
|
||||||
"2006-01-02T15:04Z0700", // ISO8601 with minutes
|
|
||||||
"2006-01-02T15:04:05Z0700", // ISO8601 with seconds
|
|
||||||
"2006-01-02T15:04:05.999999999Z0700", // ISO8601 with nanoseconds
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, format := range timeFormats {
|
|
||||||
|
|
||||||
ret, found = tryParseExactTime(candidate, format)
|
|
||||||
if found {
|
|
||||||
return ret, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Now(), false
|
|
||||||
}
|
|
||||||
|
|
||||||
func tryParseExactTime(candidate string, format string) (time.Time, bool) {
|
|
||||||
|
|
||||||
var ret time.Time
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ret, err = time.ParseInLocation(format, candidate, time.Local)
|
|
||||||
if err != nil {
|
|
||||||
return time.Now(), false
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, true
|
|
||||||
}
|
|
71
vendor/github.com/Knetic/govaluate/sanitizedParameters.go
generated
vendored
71
vendor/github.com/Knetic/govaluate/sanitizedParameters.go
generated
vendored
@ -1,71 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
// sanitizedParameters is a wrapper for Parameters that does sanitization as
|
|
||||||
// parameters are accessed.
|
|
||||||
type sanitizedParameters struct {
|
|
||||||
orig Parameters
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p sanitizedParameters) Get(key string) (interface{}, error) {
|
|
||||||
value, err := p.orig.Get(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// should be converted to fixed point?
|
|
||||||
if isFixedPoint(value) {
|
|
||||||
return castFixedPoint(value), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isFixedPoint(value interface{}) bool {
|
|
||||||
|
|
||||||
switch value.(type) {
|
|
||||||
case uint8:
|
|
||||||
return true
|
|
||||||
case uint16:
|
|
||||||
return true
|
|
||||||
case uint32:
|
|
||||||
return true
|
|
||||||
case uint64:
|
|
||||||
return true
|
|
||||||
case int8:
|
|
||||||
return true
|
|
||||||
case int16:
|
|
||||||
return true
|
|
||||||
case int32:
|
|
||||||
return true
|
|
||||||
case int64:
|
|
||||||
return true
|
|
||||||
case int:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func castFixedPoint(value interface{}) float64 {
|
|
||||||
switch value.(type) {
|
|
||||||
case uint8:
|
|
||||||
return float64(value.(uint8))
|
|
||||||
case uint16:
|
|
||||||
return float64(value.(uint16))
|
|
||||||
case uint32:
|
|
||||||
return float64(value.(uint32))
|
|
||||||
case uint64:
|
|
||||||
return float64(value.(uint64))
|
|
||||||
case int8:
|
|
||||||
return float64(value.(int8))
|
|
||||||
case int16:
|
|
||||||
return float64(value.(int16))
|
|
||||||
case int32:
|
|
||||||
return float64(value.(int32))
|
|
||||||
case int64:
|
|
||||||
return float64(value.(int64))
|
|
||||||
case int:
|
|
||||||
return float64(value.(int))
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0.0
|
|
||||||
}
|
|
675
vendor/github.com/Knetic/govaluate/stagePlanner.go
generated
vendored
675
vendor/github.com/Knetic/govaluate/stagePlanner.go
generated
vendored
@ -1,675 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var stageSymbolMap = map[OperatorSymbol]evaluationOperator{
|
|
||||||
EQ: equalStage,
|
|
||||||
NEQ: notEqualStage,
|
|
||||||
GT: gtStage,
|
|
||||||
LT: ltStage,
|
|
||||||
GTE: gteStage,
|
|
||||||
LTE: lteStage,
|
|
||||||
REQ: regexStage,
|
|
||||||
NREQ: notRegexStage,
|
|
||||||
AND: andStage,
|
|
||||||
OR: orStage,
|
|
||||||
IN: inStage,
|
|
||||||
BITWISE_OR: bitwiseOrStage,
|
|
||||||
BITWISE_AND: bitwiseAndStage,
|
|
||||||
BITWISE_XOR: bitwiseXORStage,
|
|
||||||
BITWISE_LSHIFT: leftShiftStage,
|
|
||||||
BITWISE_RSHIFT: rightShiftStage,
|
|
||||||
PLUS: addStage,
|
|
||||||
MINUS: subtractStage,
|
|
||||||
MULTIPLY: multiplyStage,
|
|
||||||
DIVIDE: divideStage,
|
|
||||||
MODULUS: modulusStage,
|
|
||||||
EXPONENT: exponentStage,
|
|
||||||
NEGATE: negateStage,
|
|
||||||
INVERT: invertStage,
|
|
||||||
BITWISE_NOT: bitwiseNotStage,
|
|
||||||
TERNARY_TRUE: ternaryIfStage,
|
|
||||||
TERNARY_FALSE: ternaryElseStage,
|
|
||||||
COALESCE: ternaryElseStage,
|
|
||||||
SEPARATE: separatorStage,
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
A "precedent" is a function which will recursively parse new evaluateionStages from a given stream of tokens.
|
|
||||||
It's called a `precedent` because it is expected to handle exactly what precedence of operator,
|
|
||||||
and defer to other `precedent`s for other operators.
|
|
||||||
*/
|
|
||||||
type precedent func(stream *tokenStream) (*evaluationStage, error)
|
|
||||||
|
|
||||||
/*
|
|
||||||
A convenience function for specifying the behavior of a `precedent`.
|
|
||||||
Most `precedent` functions can be described by the same function, just with different type checks, symbols, and error formats.
|
|
||||||
This struct is passed to `makePrecedentFromPlanner` to create a `precedent` function.
|
|
||||||
*/
|
|
||||||
type precedencePlanner struct {
|
|
||||||
validSymbols map[string]OperatorSymbol
|
|
||||||
validKinds []TokenKind
|
|
||||||
|
|
||||||
typeErrorFormat string
|
|
||||||
|
|
||||||
next precedent
|
|
||||||
nextRight precedent
|
|
||||||
}
|
|
||||||
|
|
||||||
var planPrefix precedent
|
|
||||||
var planExponential precedent
|
|
||||||
var planMultiplicative precedent
|
|
||||||
var planAdditive precedent
|
|
||||||
var planBitwise precedent
|
|
||||||
var planShift precedent
|
|
||||||
var planComparator precedent
|
|
||||||
var planLogicalAnd precedent
|
|
||||||
var planLogicalOr precedent
|
|
||||||
var planTernary precedent
|
|
||||||
var planSeparator precedent
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
// all these stages can use the same code (in `planPrecedenceLevel`) to execute,
|
|
||||||
// they simply need different type checks, symbols, and recursive precedents.
|
|
||||||
// While not all precedent phases are listed here, most can be represented this way.
|
|
||||||
planPrefix = makePrecedentFromPlanner(&precedencePlanner{
|
|
||||||
validSymbols: prefixSymbols,
|
|
||||||
validKinds: []TokenKind{PREFIX},
|
|
||||||
typeErrorFormat: prefixErrorFormat,
|
|
||||||
nextRight: planFunction,
|
|
||||||
})
|
|
||||||
planExponential = makePrecedentFromPlanner(&precedencePlanner{
|
|
||||||
validSymbols: exponentialSymbolsS,
|
|
||||||
validKinds: []TokenKind{MODIFIER},
|
|
||||||
typeErrorFormat: modifierErrorFormat,
|
|
||||||
next: planFunction,
|
|
||||||
})
|
|
||||||
planMultiplicative = makePrecedentFromPlanner(&precedencePlanner{
|
|
||||||
validSymbols: multiplicativeSymbols,
|
|
||||||
validKinds: []TokenKind{MODIFIER},
|
|
||||||
typeErrorFormat: modifierErrorFormat,
|
|
||||||
next: planExponential,
|
|
||||||
})
|
|
||||||
planAdditive = makePrecedentFromPlanner(&precedencePlanner{
|
|
||||||
validSymbols: additiveSymbols,
|
|
||||||
validKinds: []TokenKind{MODIFIER},
|
|
||||||
typeErrorFormat: modifierErrorFormat,
|
|
||||||
next: planMultiplicative,
|
|
||||||
})
|
|
||||||
planShift = makePrecedentFromPlanner(&precedencePlanner{
|
|
||||||
validSymbols: bitwiseShiftSymbols,
|
|
||||||
validKinds: []TokenKind{MODIFIER},
|
|
||||||
typeErrorFormat: modifierErrorFormat,
|
|
||||||
next: planAdditive,
|
|
||||||
})
|
|
||||||
planBitwise = makePrecedentFromPlanner(&precedencePlanner{
|
|
||||||
validSymbols: bitwiseSymbols,
|
|
||||||
validKinds: []TokenKind{MODIFIER},
|
|
||||||
typeErrorFormat: modifierErrorFormat,
|
|
||||||
next: planShift,
|
|
||||||
})
|
|
||||||
planComparator = makePrecedentFromPlanner(&precedencePlanner{
|
|
||||||
validSymbols: comparatorSymbols,
|
|
||||||
validKinds: []TokenKind{COMPARATOR},
|
|
||||||
typeErrorFormat: comparatorErrorFormat,
|
|
||||||
next: planBitwise,
|
|
||||||
})
|
|
||||||
planLogicalAnd = makePrecedentFromPlanner(&precedencePlanner{
|
|
||||||
validSymbols: map[string]OperatorSymbol{"&&": AND},
|
|
||||||
validKinds: []TokenKind{LOGICALOP},
|
|
||||||
typeErrorFormat: logicalErrorFormat,
|
|
||||||
next: planComparator,
|
|
||||||
})
|
|
||||||
planLogicalOr = makePrecedentFromPlanner(&precedencePlanner{
|
|
||||||
validSymbols: map[string]OperatorSymbol{"||": OR},
|
|
||||||
validKinds: []TokenKind{LOGICALOP},
|
|
||||||
typeErrorFormat: logicalErrorFormat,
|
|
||||||
next: planLogicalAnd,
|
|
||||||
})
|
|
||||||
planTernary = makePrecedentFromPlanner(&precedencePlanner{
|
|
||||||
validSymbols: ternarySymbols,
|
|
||||||
validKinds: []TokenKind{TERNARY},
|
|
||||||
typeErrorFormat: ternaryErrorFormat,
|
|
||||||
next: planLogicalOr,
|
|
||||||
})
|
|
||||||
planSeparator = makePrecedentFromPlanner(&precedencePlanner{
|
|
||||||
validSymbols: separatorSymbols,
|
|
||||||
validKinds: []TokenKind{SEPARATOR},
|
|
||||||
next: planTernary,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Given a planner, creates a function which will evaluate a specific precedence level of operators,
|
|
||||||
and link it to other `precedent`s which recurse to parse other precedence levels.
|
|
||||||
*/
|
|
||||||
func makePrecedentFromPlanner(planner *precedencePlanner) precedent {
|
|
||||||
|
|
||||||
var generated precedent
|
|
||||||
var nextRight precedent
|
|
||||||
|
|
||||||
generated = func(stream *tokenStream) (*evaluationStage, error) {
|
|
||||||
return planPrecedenceLevel(
|
|
||||||
stream,
|
|
||||||
planner.typeErrorFormat,
|
|
||||||
planner.validSymbols,
|
|
||||||
planner.validKinds,
|
|
||||||
nextRight,
|
|
||||||
planner.next,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if planner.nextRight != nil {
|
|
||||||
nextRight = planner.nextRight
|
|
||||||
} else {
|
|
||||||
nextRight = generated
|
|
||||||
}
|
|
||||||
|
|
||||||
return generated
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Creates a `evaluationStageList` object which represents an execution plan (or tree)
|
|
||||||
which is used to completely evaluate a set of tokens at evaluation-time.
|
|
||||||
The three stages of evaluation can be thought of as parsing strings to tokens, then tokens to a stage list, then evaluation with parameters.
|
|
||||||
*/
|
|
||||||
func planStages(tokens []ExpressionToken) (*evaluationStage, error) {
|
|
||||||
|
|
||||||
stream := newTokenStream(tokens)
|
|
||||||
|
|
||||||
stage, err := planTokens(stream)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// while we're now fully-planned, we now need to re-order same-precedence operators.
|
|
||||||
// this could probably be avoided with a different planning method
|
|
||||||
reorderStages(stage)
|
|
||||||
|
|
||||||
stage = elideLiterals(stage)
|
|
||||||
return stage, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func planTokens(stream *tokenStream) (*evaluationStage, error) {
|
|
||||||
|
|
||||||
if !stream.hasNext() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return planSeparator(stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
The most usual method of parsing an evaluation stage for a given precedence.
|
|
||||||
Most stages use the same logic
|
|
||||||
*/
|
|
||||||
func planPrecedenceLevel(
|
|
||||||
stream *tokenStream,
|
|
||||||
typeErrorFormat string,
|
|
||||||
validSymbols map[string]OperatorSymbol,
|
|
||||||
validKinds []TokenKind,
|
|
||||||
rightPrecedent precedent,
|
|
||||||
leftPrecedent precedent) (*evaluationStage, error) {
|
|
||||||
|
|
||||||
var token ExpressionToken
|
|
||||||
var symbol OperatorSymbol
|
|
||||||
var leftStage, rightStage *evaluationStage
|
|
||||||
var checks typeChecks
|
|
||||||
var err error
|
|
||||||
var keyFound bool
|
|
||||||
|
|
||||||
if leftPrecedent != nil {
|
|
||||||
|
|
||||||
leftStage, err = leftPrecedent(stream)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for stream.hasNext() {
|
|
||||||
|
|
||||||
token = stream.next()
|
|
||||||
|
|
||||||
if len(validKinds) > 0 {
|
|
||||||
|
|
||||||
keyFound = false
|
|
||||||
for _, kind := range validKinds {
|
|
||||||
if kind == token.Kind {
|
|
||||||
keyFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !keyFound {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if validSymbols != nil {
|
|
||||||
|
|
||||||
if !isString(token.Value) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
symbol, keyFound = validSymbols[token.Value.(string)]
|
|
||||||
if !keyFound {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rightPrecedent != nil {
|
|
||||||
rightStage, err = rightPrecedent(stream)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checks = findTypeChecks(symbol)
|
|
||||||
|
|
||||||
return &evaluationStage{
|
|
||||||
|
|
||||||
symbol: symbol,
|
|
||||||
leftStage: leftStage,
|
|
||||||
rightStage: rightStage,
|
|
||||||
operator: stageSymbolMap[symbol],
|
|
||||||
|
|
||||||
leftTypeCheck: checks.left,
|
|
||||||
rightTypeCheck: checks.right,
|
|
||||||
typeCheck: checks.combined,
|
|
||||||
typeErrorFormat: typeErrorFormat,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.rewind()
|
|
||||||
return leftStage, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
A special case where functions need to be of higher precedence than values, and need a special wrapped execution stage operator.
|
|
||||||
*/
|
|
||||||
func planFunction(stream *tokenStream) (*evaluationStage, error) {
|
|
||||||
|
|
||||||
var token ExpressionToken
|
|
||||||
var rightStage *evaluationStage
|
|
||||||
var err error
|
|
||||||
|
|
||||||
token = stream.next()
|
|
||||||
|
|
||||||
if token.Kind != FUNCTION {
|
|
||||||
stream.rewind()
|
|
||||||
return planValue(stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
rightStage, err = planValue(stream)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &evaluationStage{
|
|
||||||
|
|
||||||
symbol: FUNCTIONAL,
|
|
||||||
rightStage: rightStage,
|
|
||||||
operator: makeFunctionStage(token.Value.(ExpressionFunction)),
|
|
||||||
typeErrorFormat: "Unable to run function '%v': %v",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
A truly special precedence function, this handles all the "lowest-case" errata of the process, including literals, parmeters,
|
|
||||||
clauses, and prefixes.
|
|
||||||
*/
|
|
||||||
func planValue(stream *tokenStream) (*evaluationStage, error) {
|
|
||||||
|
|
||||||
var token ExpressionToken
|
|
||||||
var symbol OperatorSymbol
|
|
||||||
var ret *evaluationStage
|
|
||||||
var operator evaluationOperator
|
|
||||||
var err error
|
|
||||||
|
|
||||||
token = stream.next()
|
|
||||||
|
|
||||||
switch token.Kind {
|
|
||||||
|
|
||||||
case CLAUSE:
|
|
||||||
|
|
||||||
ret, err = planTokens(stream)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// advance past the CLAUSE_CLOSE token. We know that it's a CLAUSE_CLOSE, because at parse-time we check for unbalanced parens.
|
|
||||||
stream.next()
|
|
||||||
|
|
||||||
// the stage we got represents all of the logic contained within the parens
|
|
||||||
// but for technical reasons, we need to wrap this stage in a "noop" stage which breaks long chains of precedence.
|
|
||||||
// see github #33.
|
|
||||||
ret = &evaluationStage {
|
|
||||||
rightStage: ret,
|
|
||||||
operator: noopStageRight,
|
|
||||||
symbol: NOOP,
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
|
|
||||||
case CLAUSE_CLOSE:
|
|
||||||
|
|
||||||
// when functions have empty params, this will be hit. In this case, we don't have any evaluation stage to do,
|
|
||||||
// so we just return nil so that the stage planner continues on its way.
|
|
||||||
stream.rewind()
|
|
||||||
return nil, nil
|
|
||||||
|
|
||||||
case VARIABLE:
|
|
||||||
operator = makeParameterStage(token.Value.(string))
|
|
||||||
|
|
||||||
case NUMERIC:
|
|
||||||
fallthrough
|
|
||||||
case STRING:
|
|
||||||
fallthrough
|
|
||||||
case PATTERN:
|
|
||||||
fallthrough
|
|
||||||
case BOOLEAN:
|
|
||||||
symbol = LITERAL
|
|
||||||
operator = makeLiteralStage(token.Value)
|
|
||||||
case TIME:
|
|
||||||
symbol = LITERAL
|
|
||||||
operator = makeLiteralStage(float64(token.Value.(time.Time).Unix()))
|
|
||||||
|
|
||||||
case PREFIX:
|
|
||||||
stream.rewind()
|
|
||||||
return planPrefix(stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
if operator == nil {
|
|
||||||
errorMsg := fmt.Sprintf("Unable to plan token kind: '%s', value: '%v'", token.Kind.String(), token.Value)
|
|
||||||
return nil, errors.New(errorMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &evaluationStage{
|
|
||||||
symbol: symbol,
|
|
||||||
operator: operator,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Convenience function to pass a triplet of typechecks between `findTypeChecks` and `planPrecedenceLevel`.
|
|
||||||
Each of these members may be nil, which indicates that type does not matter for that value.
|
|
||||||
*/
|
|
||||||
type typeChecks struct {
|
|
||||||
left stageTypeCheck
|
|
||||||
right stageTypeCheck
|
|
||||||
combined stageCombinedTypeCheck
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Maps a given [symbol] to a set of typechecks to be used during runtime.
|
|
||||||
*/
|
|
||||||
func findTypeChecks(symbol OperatorSymbol) typeChecks {
|
|
||||||
|
|
||||||
switch symbol {
|
|
||||||
case GT:
|
|
||||||
fallthrough
|
|
||||||
case LT:
|
|
||||||
fallthrough
|
|
||||||
case GTE:
|
|
||||||
fallthrough
|
|
||||||
case LTE:
|
|
||||||
return typeChecks{
|
|
||||||
combined: comparatorTypeCheck,
|
|
||||||
}
|
|
||||||
case REQ:
|
|
||||||
fallthrough
|
|
||||||
case NREQ:
|
|
||||||
return typeChecks{
|
|
||||||
left: isString,
|
|
||||||
right: isRegexOrString,
|
|
||||||
}
|
|
||||||
case AND:
|
|
||||||
fallthrough
|
|
||||||
case OR:
|
|
||||||
return typeChecks{
|
|
||||||
left: isBool,
|
|
||||||
right: isBool,
|
|
||||||
}
|
|
||||||
case IN:
|
|
||||||
return typeChecks{
|
|
||||||
right: isArray,
|
|
||||||
}
|
|
||||||
case BITWISE_LSHIFT:
|
|
||||||
fallthrough
|
|
||||||
case BITWISE_RSHIFT:
|
|
||||||
fallthrough
|
|
||||||
case BITWISE_OR:
|
|
||||||
fallthrough
|
|
||||||
case BITWISE_AND:
|
|
||||||
fallthrough
|
|
||||||
case BITWISE_XOR:
|
|
||||||
return typeChecks{
|
|
||||||
left: isFloat64,
|
|
||||||
right: isFloat64,
|
|
||||||
}
|
|
||||||
case PLUS:
|
|
||||||
return typeChecks{
|
|
||||||
combined: additionTypeCheck,
|
|
||||||
}
|
|
||||||
case MINUS:
|
|
||||||
fallthrough
|
|
||||||
case MULTIPLY:
|
|
||||||
fallthrough
|
|
||||||
case DIVIDE:
|
|
||||||
fallthrough
|
|
||||||
case MODULUS:
|
|
||||||
fallthrough
|
|
||||||
case EXPONENT:
|
|
||||||
return typeChecks{
|
|
||||||
left: isFloat64,
|
|
||||||
right: isFloat64,
|
|
||||||
}
|
|
||||||
case NEGATE:
|
|
||||||
return typeChecks{
|
|
||||||
right: isFloat64,
|
|
||||||
}
|
|
||||||
case INVERT:
|
|
||||||
return typeChecks{
|
|
||||||
right: isBool,
|
|
||||||
}
|
|
||||||
case BITWISE_NOT:
|
|
||||||
return typeChecks{
|
|
||||||
right: isFloat64,
|
|
||||||
}
|
|
||||||
case TERNARY_TRUE:
|
|
||||||
return typeChecks{
|
|
||||||
left: isBool,
|
|
||||||
}
|
|
||||||
|
|
||||||
// unchecked cases
|
|
||||||
case EQ:
|
|
||||||
fallthrough
|
|
||||||
case NEQ:
|
|
||||||
return typeChecks{}
|
|
||||||
case TERNARY_FALSE:
|
|
||||||
fallthrough
|
|
||||||
case COALESCE:
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
return typeChecks{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
During stage planning, stages of equal precedence are parsed such that they'll be evaluated in reverse order.
|
|
||||||
For commutative operators like "+" or "-", it's no big deal. But for order-specific operators, it ruins the expected result.
|
|
||||||
*/
|
|
||||||
func reorderStages(rootStage *evaluationStage) {
|
|
||||||
|
|
||||||
// traverse every rightStage until we find multiples in a row of the same precedence.
|
|
||||||
var identicalPrecedences []*evaluationStage
|
|
||||||
var currentStage, nextStage *evaluationStage
|
|
||||||
var precedence, currentPrecedence operatorPrecedence
|
|
||||||
|
|
||||||
nextStage = rootStage
|
|
||||||
precedence = findOperatorPrecedenceForSymbol(rootStage.symbol)
|
|
||||||
|
|
||||||
for nextStage != nil {
|
|
||||||
|
|
||||||
currentStage = nextStage
|
|
||||||
nextStage = currentStage.rightStage
|
|
||||||
|
|
||||||
// left depth first, since this entire method only looks for precedences down the right side of the tree
|
|
||||||
if currentStage.leftStage != nil {
|
|
||||||
reorderStages(currentStage.leftStage)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPrecedence = findOperatorPrecedenceForSymbol(currentStage.symbol)
|
|
||||||
|
|
||||||
if currentPrecedence == precedence {
|
|
||||||
identicalPrecedences = append(identicalPrecedences, currentStage)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// precedence break.
|
|
||||||
// See how many in a row we had, and reorder if there's more than one.
|
|
||||||
if len(identicalPrecedences) > 1 {
|
|
||||||
mirrorStageSubtree(identicalPrecedences)
|
|
||||||
}
|
|
||||||
|
|
||||||
identicalPrecedences = []*evaluationStage{currentStage}
|
|
||||||
precedence = currentPrecedence
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(identicalPrecedences) > 1 {
|
|
||||||
mirrorStageSubtree(identicalPrecedences)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Performs a "mirror" on a subtree of stages.
|
|
||||||
This mirror functionally inverts the order of execution for all members of the [stages] list.
|
|
||||||
That list is assumed to be a root-to-leaf (ordered) list of evaluation stages, where each is a right-hand stage of the last.
|
|
||||||
*/
|
|
||||||
func mirrorStageSubtree(stages []*evaluationStage) {
|
|
||||||
|
|
||||||
var rootStage, inverseStage, carryStage, frontStage *evaluationStage
|
|
||||||
|
|
||||||
stagesLength := len(stages)
|
|
||||||
|
|
||||||
// reverse all right/left
|
|
||||||
for _, frontStage = range stages {
|
|
||||||
|
|
||||||
carryStage = frontStage.rightStage
|
|
||||||
frontStage.rightStage = frontStage.leftStage
|
|
||||||
frontStage.leftStage = carryStage
|
|
||||||
}
|
|
||||||
|
|
||||||
// end left swaps with root right
|
|
||||||
rootStage = stages[0]
|
|
||||||
frontStage = stages[stagesLength-1]
|
|
||||||
|
|
||||||
carryStage = frontStage.leftStage
|
|
||||||
frontStage.leftStage = rootStage.rightStage
|
|
||||||
rootStage.rightStage = carryStage
|
|
||||||
|
|
||||||
// for all non-root non-end stages, right is swapped with inverse stage right in list
|
|
||||||
for i := 0; i < (stagesLength-2)/2+1; i++ {
|
|
||||||
|
|
||||||
frontStage = stages[i+1]
|
|
||||||
inverseStage = stages[stagesLength-i-1]
|
|
||||||
|
|
||||||
carryStage = frontStage.rightStage
|
|
||||||
frontStage.rightStage = inverseStage.rightStage
|
|
||||||
inverseStage.rightStage = carryStage
|
|
||||||
}
|
|
||||||
|
|
||||||
// swap all other information with inverse stages
|
|
||||||
for i := 0; i < stagesLength/2; i++ {
|
|
||||||
|
|
||||||
frontStage = stages[i]
|
|
||||||
inverseStage = stages[stagesLength-i-1]
|
|
||||||
frontStage.swapWith(inverseStage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Recurses through all operators in the entire tree, eliding operators where both sides are literals.
|
|
||||||
*/
|
|
||||||
func elideLiterals(root *evaluationStage) *evaluationStage {
|
|
||||||
|
|
||||||
if root.leftStage != nil {
|
|
||||||
root.leftStage = elideLiterals(root.leftStage)
|
|
||||||
}
|
|
||||||
|
|
||||||
if root.rightStage != nil {
|
|
||||||
root.rightStage = elideLiterals(root.rightStage)
|
|
||||||
}
|
|
||||||
|
|
||||||
return elideStage(root)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Elides a specific stage, if possible.
|
|
||||||
Returns the unmodified [root] stage if it cannot or should not be elided.
|
|
||||||
Otherwise, returns a new stage representing the condensed value from the elided stages.
|
|
||||||
*/
|
|
||||||
func elideStage(root *evaluationStage) *evaluationStage {
|
|
||||||
|
|
||||||
var leftValue, rightValue, result interface{}
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// right side must be a non-nil value. Left side must be nil or a value.
|
|
||||||
if root.rightStage == nil ||
|
|
||||||
root.rightStage.symbol != LITERAL ||
|
|
||||||
root.leftStage == nil ||
|
|
||||||
root.leftStage.symbol != LITERAL {
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't elide some operators
|
|
||||||
switch root.symbol {
|
|
||||||
case SEPARATE:
|
|
||||||
fallthrough
|
|
||||||
case IN:
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
// both sides are values, get their actual values.
|
|
||||||
// errors should be near-impossible here. If we encounter them, just abort this optimization.
|
|
||||||
leftValue, err = root.leftStage.operator(nil, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
rightValue, err = root.rightStage.operator(nil, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
// typcheck, since the grammar checker is a bit loose with which operator symbols go together.
|
|
||||||
err = typeCheck(root.leftTypeCheck, leftValue, root.symbol, root.typeErrorFormat)
|
|
||||||
if err != nil {
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
err = typeCheck(root.rightTypeCheck, rightValue, root.symbol, root.typeErrorFormat)
|
|
||||||
if err != nil {
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
if root.typeCheck != nil && !root.typeCheck(leftValue, rightValue) {
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre-calculate, and return a new stage representing the result.
|
|
||||||
result, err = root.operator(leftValue, rightValue, nil)
|
|
||||||
if err != nil {
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
return &evaluationStage {
|
|
||||||
symbol: LITERAL,
|
|
||||||
operator: makeLiteralStage(result),
|
|
||||||
}
|
|
||||||
}
|
|
32
vendor/github.com/Knetic/govaluate/test.sh
generated
vendored
32
vendor/github.com/Knetic/govaluate/test.sh
generated
vendored
@ -1,32 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Script that runs tests, code coverage, and benchmarks all at once.
|
|
||||||
# Builds a symlink in /tmp, mostly to avoid messing with GOPATH at the user's shell level.
|
|
||||||
|
|
||||||
TEMPORARY_PATH="/tmp/govaluate_test"
|
|
||||||
SRC_PATH="${TEMPORARY_PATH}/src"
|
|
||||||
FULL_PATH="${TEMPORARY_PATH}/src/govaluate"
|
|
||||||
|
|
||||||
# set up temporary directory
|
|
||||||
rm -rf "${FULL_PATH}"
|
|
||||||
mkdir -p "${SRC_PATH}"
|
|
||||||
|
|
||||||
ln -s $(pwd) "${FULL_PATH}"
|
|
||||||
export GOPATH="${TEMPORARY_PATH}"
|
|
||||||
|
|
||||||
pushd "${TEMPORARY_PATH}/src/govaluate"
|
|
||||||
|
|
||||||
# run the actual tests.
|
|
||||||
export GOVALUATE_TORTURE_TEST="true"
|
|
||||||
go test -bench=. -benchmem -coverprofile coverage.out
|
|
||||||
status=$?
|
|
||||||
|
|
||||||
if [ "${status}" != 0 ];
|
|
||||||
then
|
|
||||||
exit $status
|
|
||||||
fi
|
|
||||||
|
|
||||||
# coverage
|
|
||||||
go tool cover -func=coverage.out
|
|
||||||
|
|
||||||
popd
|
|
36
vendor/github.com/Knetic/govaluate/tokenStream.go
generated
vendored
36
vendor/github.com/Knetic/govaluate/tokenStream.go
generated
vendored
@ -1,36 +0,0 @@
|
|||||||
package govaluate
|
|
||||||
|
|
||||||
type tokenStream struct {
|
|
||||||
tokens []ExpressionToken
|
|
||||||
index int
|
|
||||||
tokenLength int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTokenStream(tokens []ExpressionToken) *tokenStream {
|
|
||||||
|
|
||||||
var ret *tokenStream
|
|
||||||
|
|
||||||
ret = new(tokenStream)
|
|
||||||
ret.tokens = tokens
|
|
||||||
ret.tokenLength = len(tokens)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *tokenStream) rewind() {
|
|
||||||
this.index -= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *tokenStream) next() ExpressionToken {
|
|
||||||
|
|
||||||
var token ExpressionToken
|
|
||||||
|
|
||||||
token = this.tokens[this.index]
|
|
||||||
|
|
||||||
this.index += 1
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this tokenStream) hasNext() bool {
|
|
||||||
|
|
||||||
return this.index < this.tokenLength
|
|
||||||
}
|
|
22
vendor/github.com/beego/goyaml2/.gitignore
generated
vendored
22
vendor/github.com/beego/goyaml2/.gitignore
generated
vendored
@ -1,22 +0,0 @@
|
|||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user