bump deps

This commit is contained in:
Umputun
2021-01-03 03:55:00 -06:00
parent a0b2bbf1ed
commit ed3cd197dd
64 changed files with 2945 additions and 381 deletions
+1 -1
View File
@@ -977,5 +977,5 @@ func (c *authRefreshCache) Get(key interface{}) (interface{}, bool) {
// Set implements cache setter with key converted to string
func (c *authRefreshCache) Set(key, value interface{}) {
_, _ = c.LoadingCache.Get(key.(string), func() (cache.Value, error) { return value, nil })
_, _ = c.LoadingCache.Get(key.(string), func() (interface{}, error) { return value, nil })
}
+1 -1
View File
@@ -507,7 +507,7 @@ func (s *DataStore) HasReplies(comment store.Comment) bool {
// When this code is reached, key "comment.ID" is not in cache.
// Calling cache.Get on it will put it in cache with 5 minutes TTL.
// We call it with empty struct as value as we care about keys and not values.
_, _ = s.repliesCache.Get(comment.ID, func() (lcw.Value, error) { return struct{}{}, nil })
_, _ = s.repliesCache.Get(comment.ID, func() (interface{}, error) { return struct{}{}, nil })
return true
}
}
+2 -2
View File
@@ -40,7 +40,7 @@ func NewTitleExtractor(client http.Client) *TitleExtractor {
// Get page for url and return title
func (t *TitleExtractor) Get(url string) (string, error) {
client := http.Client{Timeout: t.client.Timeout, Transport: t.client.Transport}
b, err := t.cache.Get(url, func() (lcw.Value, error) {
b, err := t.cache.Get(url, func() (interface{}, error) {
resp, err := client.Get(url)
if err != nil {
return nil, errors.Wrapf(err, "failed to load page %s", url)
@@ -63,7 +63,7 @@ func (t *TitleExtractor) Get(url string) (string, error) {
// on error save result (empty string) to cache too and return "" title
if err != nil {
_, _ = t.cache.Get(url, func() (lcw.Value, error) { return "", nil })
_, _ = t.cache.Get(url, func() (interface{}, error) { return "", nil })
return "", err
}
+5 -5
View File
@@ -14,12 +14,12 @@ require (
github.com/go-chi/render v1.0.1
github.com/go-pkgz/auth v1.13.1
github.com/go-pkgz/jrpc v0.2.0
github.com/go-pkgz/lcw v0.7.1
github.com/go-pkgz/lgr v0.7.0
github.com/go-pkgz/lcw v0.8.1
github.com/go-pkgz/lgr v0.10.4
github.com/go-pkgz/repeater v1.1.3
github.com/go-pkgz/rest v1.5.0
github.com/go-pkgz/syncs v1.1.1
github.com/google/uuid v1.1.1
github.com/google/uuid v1.1.2
github.com/gorilla/feeds v1.1.1
github.com/hashicorp/go-multierror v1.1.0
github.com/kyokomi/emoji v2.2.1+incompatible
@@ -27,10 +27,10 @@ require (
github.com/pkg/errors v0.9.1
github.com/rakyll/statik v0.1.7
github.com/rs/xid v1.2.1
github.com/russross/blackfriday/v2 v2.0.1
github.com/russross/blackfriday/v2 v2.1.0
github.com/stretchr/testify v1.6.1
github.com/umputun/go-flags v1.5.1
go.etcd.io/bbolt v1.3.4
go.etcd.io/bbolt v1.3.5
go.uber.org/goleak v1.0.0
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71
golang.org/x/image v0.0.0-20200119044424-58c23975cae1
+12 -8
View File
@@ -64,18 +64,18 @@ github.com/go-pkgz/expirable-cache v0.0.3 h1:rTh6qNPp78z0bQE6HDhXBHUwqnV9i09Vm6d
github.com/go-pkgz/expirable-cache v0.0.3/go.mod h1:+IauqN00R2FqNRLCLA+X5YljQJrwB179PfiAoMPlTlQ=
github.com/go-pkgz/jrpc v0.2.0 h1:CLy/eZyekjraVrxZV18N2R1mYLMJ/nWrgdfyIOGPY/E=
github.com/go-pkgz/jrpc v0.2.0/go.mod h1:wd8vtQ4CgtCnuqua6x2b1SKIgv0VSOh5Dn0uUITbiUE=
github.com/go-pkgz/lcw v0.7.1 h1:2Q2k1am6xb2KgxZG84kL072q66rlWFUR+MvgPcDcJXA=
github.com/go-pkgz/lcw v0.7.1/go.mod h1:3P6g9QrJsDePXEMe42ywO+tW08L17tBJGwIDdI7lZ6g=
github.com/go-pkgz/lgr v0.7.0 h1:S/AAPwt/RE9a5mNJskA7dGVp+Dq6SMIW6LYjG3ITxY8=
github.com/go-pkgz/lgr v0.7.0/go.mod h1:yMgxU+GobMRJgIEbSzDKy/67W18S7qmGx/7BVL5AB8Q=
github.com/go-pkgz/lcw v0.8.1 h1:Bpt2yYTE1J8hIhz8tjdm1WPOgH13eo5iTNsXyop7cMQ=
github.com/go-pkgz/lcw v0.8.1/go.mod h1:Xw0/ZfApATgbjVPYRZO4XHdWyxAjErDWDWJ7TLlw1Vc=
github.com/go-pkgz/lgr v0.10.4 h1:l7qyFjqEZgwRgaQQSEp6tve4A3OU80VrfzpvtEX8ngw=
github.com/go-pkgz/lgr v0.10.4/go.mod h1:CD0s1z6EFpIUplV067gitF77tn25JItzwHNKAPqeCF0=
github.com/go-pkgz/repeater v1.1.3 h1:q6+JQF14ESSy28Dd7F+wRelY4F+41HJ0LEy/szNnMiE=
github.com/go-pkgz/repeater v1.1.3/go.mod h1:hVTavuO5x3Gxnu8zW7d6sQBfAneKV8X2FjU48kGfpKw=
github.com/go-pkgz/rest v1.5.0 h1:C8SxXcXza4GiUUAn/95iCkvoIrGbS30qpwK19iqlrWQ=
github.com/go-pkgz/rest v1.5.0/go.mod h1:nQaM3RhSTUAmbBZWY4hfe4buyeC9VckvhoCktiQXJxI=
github.com/go-pkgz/syncs v1.1.1 h1:jWN+y6FS/Xe+8z4l3QMbSnODGyaxDHGojIS+wyKIjxg=
github.com/go-pkgz/syncs v1.1.1/go.mod h1:bt9lxWRRJ9vOCMGc8Big8ttjYHLKP88ofj1y38UlaHE=
github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=
github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4=
github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
@@ -115,8 +115,8 @@ github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
@@ -197,6 +197,8 @@ github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
@@ -266,6 +268,8 @@ github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.mongodb.org/mongo-driver v1.4.4 h1:bsPHfODES+/yx2PCWzUYMH8xj6PVniPI8DQrsJuSXSs=
go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.uber.org/goleak v1.0.0 h1:qsup4IcBdlmsnGfqyLl4Ntn3C2XCCuKAE7DwHpScyUo=
+16 -16
View File
@@ -12,9 +12,6 @@ import (
"fmt"
)
// Value type wraps interface{}
type Value interface{}
// Sizer allows to perform size-based restrictions, optional.
// If not defined both maxValueSize and maxCacheSize checks will be ignored
type Sizer interface {
@@ -23,14 +20,14 @@ type Sizer interface {
// LoadingCache defines guava-like cache with Get method returning cached value ao retrieving it if not in cache
type LoadingCache interface {
Get(key string, fn func() (Value, error)) (val Value, err error) // load or get from cache
Peek(key string) (Value, bool) // get from cache by key
Invalidate(fn func(key string) bool) // invalidate items for func(key) == true
Delete(key string) // delete by key
Purge() // clear cache
Stat() CacheStat // cache stats
Keys() []string // list of all keys
Close() error // close open connections
Get(key string, fn func() (interface{}, error)) (val interface{}, err error) // load or get from cache
Peek(key string) (interface{}, bool) // get from cache by key
Invalidate(fn func(key string) bool) // invalidate items for func(key) == true
Delete(key string) // delete by key
Purge() // clear cache
Stat() CacheStat // cache stats
Keys() []string // list of all keys
Close() error // close open connections
}
// CacheStat represent stats values
@@ -44,8 +41,12 @@ type CacheStat struct {
// String formats cache stats
func (s CacheStat) String() string {
return fmt.Sprintf("{hits:%d, misses:%d, ratio:%.1f%%, keys:%d, size:%d, errors:%d}",
s.Hits, s.Misses, 100*(float64(s.Hits)/float64(s.Hits+s.Misses)), s.Keys, s.Size, s.Errors)
ratio := 0.0
if s.Hits+s.Misses > 0 {
ratio = float64(s.Hits) / float64(s.Hits+s.Misses)
}
return fmt.Sprintf("{hits:%d, misses:%d, ratio:%.2f, keys:%d, size:%d, errors:%d}",
s.Hits, s.Misses, ratio, s.Keys, s.Size, s.Errors)
}
// Nop is do-nothing implementation of LoadingCache
@@ -57,10 +58,10 @@ func NewNopCache() *Nop {
}
// Get calls fn without any caching
func (n *Nop) Get(key string, fn func() (Value, error)) (Value, error) { return fn() }
func (n *Nop) Get(key string, fn func() (interface{}, error)) (interface{}, error) { return fn() }
// Peek does nothing and always returns false
func (n *Nop) Peek(key string) (Value, bool) { return nil, false }
func (n *Nop) Peek(key string) (interface{}, bool) { return nil, false }
// Invalidate does nothing for nop cache
func (n *Nop) Invalidate(fn func(key string) bool) {}
@@ -83,4 +84,3 @@ func (n *Nop) Stat() CacheStat {
func (n *Nop) Close() error {
return nil
}
+3 -3
View File
@@ -69,7 +69,7 @@ func NewExpirableCache(opts ...Option) (*ExpirableCache, error) {
}
// Get gets value by key or load with fn if not found in cache
func (c *ExpirableCache) Get(key string, fn func() (Value, error)) (data Value, err error) {
func (c *ExpirableCache) Get(key string, fn func() (interface{}, error)) (data interface{}, err error) {
if v, ok := c.backend.Get(key); ok {
atomic.AddInt64(&c.Hits, 1)
return v, nil
@@ -104,7 +104,7 @@ func (c *ExpirableCache) Invalidate(fn func(key string) bool) {
}
// Peek returns the key value (or undefined if not found) without updating the "recently used"-ness of the key.
func (c *ExpirableCache) Peek(key string) (Value, bool) {
func (c *ExpirableCache) Peek(key string) (interface{}, bool) {
return c.backend.Peek(key)
}
@@ -156,7 +156,7 @@ func (c *ExpirableCache) keys() int {
return c.backend.ItemCount()
}
func (c *ExpirableCache) allowed(key string, data Value) bool {
func (c *ExpirableCache) allowed(key string, data interface{}) bool {
if c.backend.ItemCount() >= c.maxKeys {
return false
}
+5 -5
View File
@@ -1,13 +1,13 @@
module github.com/go-pkgz/lcw
go 1.15
require (
github.com/alicebob/miniredis/v2 v2.11.4
github.com/go-redis/redis/v7 v7.2.0
github.com/google/uuid v1.1.1
github.com/go-redis/redis/v7 v7.4.0
github.com/google/uuid v1.1.2
github.com/hashicorp/go-multierror v1.1.0
github.com/hashicorp/golang-lru v0.5.4
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.5.1
github.com/stretchr/testify v1.6.1
)
go 1.13
+9 -7
View File
@@ -9,15 +9,15 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=
github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4=
github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3 h1:6amM4HsNPOvMLVc2ZnyqrjeQ92YAVWn7T4WBKK87inY=
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
@@ -40,9 +40,10 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/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/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -67,6 +68,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+3 -3
View File
@@ -65,7 +65,7 @@ func (c *LruCache) init() error {
}
// Get gets value by key or load with fn if not found in cache
func (c *LruCache) Get(key string, fn func() (Value, error)) (data Value, err error) {
func (c *LruCache) Get(key string, fn func() (interface{}, error)) (data interface{}, err error) {
if v, ok := c.backend.Get(key); ok {
atomic.AddInt64(&c.Hits, 1)
return v, nil
@@ -97,7 +97,7 @@ func (c *LruCache) Get(key string, fn func() (Value, error)) (data Value, err er
}
// Peek returns the key value (or undefined if not found) without updating the "recently used"-ness of the key.
func (c *LruCache) Peek(key string) (Value, bool) {
func (c *LruCache) Peek(key string) (interface{}, bool) {
return c.backend.Peek(key)
}
@@ -162,7 +162,7 @@ func (c *LruCache) keys() int {
return c.backend.Len()
}
func (c *LruCache) allowed(key string, data Value) bool {
func (c *LruCache) allowed(key string, data interface{}) bool {
if c.maxKeySize > 0 && len(key) > c.maxKeySize {
return false
}
+2 -2
View File
@@ -13,7 +13,7 @@ type options struct {
maxKeySize int
maxCacheSize int64
ttl time.Duration
onEvicted func(key string, value Value)
onEvicted func(key string, value interface{})
eventBus eventbus.PubSub
}
@@ -81,7 +81,7 @@ func TTL(ttl time.Duration) Option {
}
// OnEvicted sets callback on invalidation event
func OnEvicted(fn func(key string, value Value)) Option {
func OnEvicted(fn func(key string, value interface{})) Option {
return func(o *options) error {
o.onEvicted = fn
return nil
+3 -3
View File
@@ -41,7 +41,7 @@ func NewRedisCache(backend *redis.Client, opts ...Option) (*RedisCache, error) {
}
// Get gets value by key or load with fn if not found in cache
func (c *RedisCache) Get(key string, fn func() (Value, error)) (data Value, err error) {
func (c *RedisCache) Get(key string, fn func() (interface{}, error)) (data interface{}, err error) {
v, getErr := c.backend.Get(key).Result()
switch getErr {
// RedisClient returns nil when find a key in DB
@@ -84,7 +84,7 @@ func (c *RedisCache) Invalidate(fn func(key string) bool) {
}
// Peek returns the key value (or undefined if not found) without updating the "recently used"-ness of the key.
func (c *RedisCache) Peek(key string) (Value, bool) {
func (c *RedisCache) Peek(key string) (interface{}, bool) {
ret, err := c.backend.Get(key).Result()
if err != nil {
return nil, false
@@ -132,7 +132,7 @@ func (c *RedisCache) keys() int {
return int(c.backend.DBSize().Val())
}
func (c *RedisCache) allowed(key string, data Value) bool {
func (c *RedisCache) allowed(key string, data interface{}) bool {
if c.maxKeys > 0 && c.backend.DBSize().Val() >= int64(c.maxKeys) {
return false
}
+1 -1
View File
@@ -20,7 +20,7 @@ func NewScache(lc LoadingCache) *Scache {
// Get retrieves a key from underlying backend
func (m *Scache) Get(key Key, fn func() ([]byte, error)) (data []byte, err error) {
keyStr := key.String()
val, err := m.lc.Get(keyStr, func() (value Value, e error) {
val, err := m.lc.Get(keyStr, func() (value interface{}, e error) {
return fn()
})
return val.([]byte), err
+18 -8
View File
@@ -23,11 +23,12 @@ linters-settings:
- experimental
disabled-checks:
- wrapperFunc
- hugeParam
linters:
disable-all: true
enable:
- megacheck
- golint
- govet
- unconvert
- megacheck
@@ -42,19 +43,28 @@ linters:
- typecheck
- ineffassign
- varcheck
- stylecheck
- gochecknoinits
- scopelint
- gocritic
- nakedret
- gosimple
- prealloc
fast: false
disable-all: true
run:
# modules-download-mode: vendor
output:
format: tab
skip-dirs:
- vendor
issues:
exclude-rules:
- text: "weak cryptographic primitive"
- text: "should have a package comment, unless it's in another file for this package"
linters:
- gosec
service:
golangci-lint-version: 1.16.x
- golint
- text: "at least one file in a package should have a package comment"
linters:
- stylecheck
exclude-use-default: false
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 Umputun
Copyright (c) 2020 Umputun
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+38 -9
View File
@@ -1,4 +1,5 @@
# lgr - simple logger with some extras [![Build Status](https://github.com/go-pkgz/lgr/workflows/build/badge.svg)](https://github.com/go-pkgz/lgr/actions) [![Coverage Status](https://coveralls.io/repos/github/go-pkgz/lgr/badge.svg?branch=master)](https://coveralls.io/github/go-pkgz/lgr?branch=master) [![godoc](https://godoc.org/github.com/go-pkgz/lgr?status.svg)](https://godoc.org/github.com/go-pkgz/lgr)
# lgr - simple logger with some extras
[![Build Status](https://github.com/go-pkgz/lgr/workflows/build/badge.svg)](https://github.com/go-pkgz/lgr/actions) [![Coverage Status](https://coveralls.io/repos/github/go-pkgz/lgr/badge.svg?branch=master)](https://coveralls.io/github/go-pkgz/lgr?branch=master) [![godoc](https://godoc.org/github.com/go-pkgz/lgr?status.svg)](https://godoc.org/github.com/go-pkgz/lgr)
## install
@@ -24,7 +25,7 @@ _Without `lgr.Caller*` it will drop `{caller}` part_
### interfaces and default loggers
- `lgr` package provides a single interface `lgr.L` with a single method `Logf(format string, args ...interface{})`. Function wrapper `lgr.Func` allows to make `lgr.L` from a function directly.
- `lgr` package provides a single interface `lgr.L` with a single method `Logf(format string, args ...interface{})`. Function wrapper `lgr.Func` allows making `lgr.L` from a function directly.
- Default logger functionality can be used without `lgr.New` (see "global logger")
- Two predefined loggers available: `lgr.NoOp` (do-nothing logger) and `lgr.Std` (passing directly to stdlib log)
@@ -41,8 +42,10 @@ _Without `lgr.Caller*` it will drop `{caller}` part_
- `lgr.CallerPkg` - adds the caller package
- `lgr.LevelBraces` - wraps levels with "[" and "]"
- `lgr.Msec` - adds milliseconds to timestamp
- `lgr.Format` - sets custom template, overwrite all other formatting modifiers.
- `lgr.Format` - sets a custom template, overwrite all other formatting modifiers.
- `lgr.Secret(secret ...)` - sets list of the secrets to hide from the logging outputs.
- `lgr.Map(mapper)` - sets mapper functions to change elements of the logging output based on levels.
- `lgr.StackTraceOnError` - turns on stack trace for ERROR level.
example: `l := lgr.New(lgr.Debug, lgr.Msec)`
@@ -61,16 +64,16 @@ Several predefined templates provided and can be passed directly to `lgr.Format`
User can make a custom template and pass it directly to `lgr.Format`. For example:
```go
```go
lgr.Format(`{{.Level}} - {{.DT.Format "2006-01-02T15:04:05Z07:00"}} - {{.CallerPkg}} - {{.Message}}`)
```
_Note: formatter (predefined or custom) adds measurable overhead - the cost will depend on the version of Go, but is between 30
and 50% in recent tests with 1.12. You can validate this in your environment via benchmarks: `go test -bench=. -run=Bench`_
### levels
`lgr.Logf` recognizes prefixes like "INFO" or "[INFO]" as levels. The full list of supported levels - "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "PANIC" and "FATAL"
`lgr.Logf` recognize prefixes like `INFO` or `[INFO]` as levels. The full list of supported levels - `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `PANIC` and `FATAL`.
- `TRACE` will be filtered unless `lgr.Trace` option defined
- `DEBUG` will be filtered unless `lgr.Debug` or `lgr.Trace` options defined
@@ -78,7 +81,29 @@ _Note: formatter (predefined or custom) adds measurable overhead - the cost will
- `ERROR` sends messages to both out and err writers
- `FATAL` and send messages to both out and err writers and exit(1)
- `PANIC` does the same as `FATAL` but in addition sends dump of callers and runtime info to err.
### mapper
Elements of the output can be altered with a set of user defined function passed as `lgr.Map` options. Such a mapper changes
the value of an element (i.e. timestamp, level, message, caller) and has separate functions for each level. Note: both level
and messages elements handled by the same function for a given level.
_A typical use-case is to produce colorful output with a user-define colorization library._
example with [fatih/color](https://github.com/fatih/color):
```go
colorizer := lgr.Mapper{
ErrorFunc: func(s string) string { return color.New(color.FgHiRed).Sprint(s) },
WarnFunc: func(s string) string { return color.New(color.FgHiYellow).Sprint(s) },
InfoFunc: func(s string) string { return color.New(color.FgHiWhite).Sprint(s) },
DebugFunc: func(s string) string { return color.New(color.FgWhite).Sprint(s) },
CallerFunc: func(s string) string { return color.New(color.FgBlue).Sprint(s) },
TimeFunc: func(s string) string { return color.New(color.FgCyan).Sprint(s) },
}
logOpts := []lgr.Option{lgr.Msec, lgr.LevelBraces, lgr.Map(colorizer)}
```
### adaptors
`lgr` logger can be converted to `io.Writer` or `*log.Logger`
@@ -86,10 +111,14 @@ _Note: formatter (predefined or custom) adds measurable overhead - the cost will
- `lgr.ToWriter(l lgr.L, level string) io.Writer` - makes io.Writer forwarding write ops to underlying `lgr.L`
- `lgr.ToStdLogger(l lgr.L, level string) *log.Logger` - makes standard logger on top of `lgr.L`
_`level` parameter is optional, if defined (non-empty) will enforce the level._
_`level` parameter is optional, if defined (non-empty) will enforce the level._
- `lgr.SetupStdLogger(opts ...Option)` initializes std global logger (`log.std`) with lgr logger and given options.
All standard methods like `log.Print`, `log.Println`, `log.Fatal` and so on will be forwarder to lgr.
### global logger
Users **should avoid** global logger and pass the concrete logger as a dependency. However, in some cases a global logger may be needed, for example migration from stdlib `log` to `lgr`. For such cases `log "github.com/go-pkgz/lgr"` can be imported instead of `log` package.
Global logger provides `lgr.Printf`, `lgr.Print` and `lgr.Fatalf` functions. User can customize the logger by calling `lgr.Setup(options ...)`. The instance of this logger can be retrieved with `lgr.Default()`
+11
View File
@@ -25,6 +25,17 @@ func ToWriter(l L, level string) *Writer {
return &Writer{l, level}
}
// ToStdLogger makes standard logger
func ToStdLogger(l L, level string) *log.Logger {
return log.New(ToWriter(l, level), "", 0)
}
// SetupStdLogger makes the default std logger with lgr.L
func SetupStdLogger(opts ...Option) {
logOpts := append([]Option{CallerDepth(3)}, opts...) // skip 3 more frames to compensate stdlog calls
l := New(logOpts...)
l.reTrace = reTraceStd // std logger split on log/ path
log.SetOutput(ToWriter(l, ""))
log.SetPrefix("")
log.SetFlags(0)
}
+2 -2
View File
@@ -1,5 +1,5 @@
module github.com/go-pkgz/lgr
require github.com/stretchr/testify v1.3.0
require github.com/stretchr/testify v1.6.1
go 1.13
go 1.15
+7 -2
View File
@@ -2,6 +2,11 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+95 -20
View File
@@ -6,7 +6,6 @@
// Debug and trace levels can be filtered based on lgr.Trace and lgr.Debug options.
// ERROR, FATAL and PANIC levels send to err as well. FATAL terminate caller application with os.Exit(1)
// and PANIC also prints stack trace.
package lgr
import (
@@ -15,6 +14,7 @@ import (
"io"
"os"
"path"
"regexp"
"runtime"
"strconv"
"strings"
@@ -26,16 +26,27 @@ import (
var levels = []string{"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "PANIC", "FATAL"}
const (
Short = `{{.DT.Format "2006/01/02 15:04:05"}} {{.Level}} {{.Message}}`
WithMsec = `{{.DT.Format "2006/01/02 15:04:05.000"}} {{.Level}} {{.Message}}`
WithPkg = `{{.DT.Format "2006/01/02 15:04:05.000"}} {{.Level}} ({{.CallerPkg}}) {{.Message}}`
// Short logging format
Short = `{{.DT.Format "2006/01/02 15:04:05"}} {{.Level}} {{.Message}}`
// WithMsec is a logging format with milliseconds
WithMsec = `{{.DT.Format "2006/01/02 15:04:05.000"}} {{.Level}} {{.Message}}`
// WithPkg is WithMsec logging format with caller package
WithPkg = `{{.DT.Format "2006/01/02 15:04:05.000"}} {{.Level}} ({{.CallerPkg}}) {{.Message}}`
// ShortDebug is WithMsec logging format with caller file and line
ShortDebug = `{{.DT.Format "2006/01/02 15:04:05.000"}} {{.Level}} ({{.CallerFile}}:{{.CallerLine}}) {{.Message}}`
FuncDebug = `{{.DT.Format "2006/01/02 15:04:05.000"}} {{.Level}} ({{.CallerFunc}}) {{.Message}}`
FullDebug = `{{.DT.Format "2006/01/02 15:04:05.000"}} {{.Level}} ({{.CallerFile}}:{{.CallerLine}} {{.CallerFunc}}) {{.Message}}`
// FuncDebug is WithMsec logging format with caller function
FuncDebug = `{{.DT.Format "2006/01/02 15:04:05.000"}} {{.Level}} ({{.CallerFunc}}) {{.Message}}`
// FullDebug is WithMsec logging format with caller file, line and function
FullDebug = `{{.DT.Format "2006/01/02 15:04:05.000"}} {{.Level}} ({{.CallerFile}}:{{.CallerLine}} {{.CallerFunc}}) {{.Message}}`
)
var secretReplacement = []byte("******")
var (
reTraceDefault = regexp.MustCompile(`.*/lgr/logger\.go.*\n`)
reTraceStd = regexp.MustCompile(`.*/log/log\.go.*\n`)
)
// Logger provided simple logger with basic support of levels. Thread safe
type Logger struct {
// set with Option calls
@@ -48,7 +59,8 @@ type Logger struct {
levelBraces bool // encloses level with [], i.e. [INFO]
callerDepth int // how many stack frames to skip, relative to the real (reported) frame
format string // layout template
secrets []string // sub-strings to secrets by matching
secrets [][]byte // sub-strings to secrets by matching
mapper Mapper // map (alter) output based on levels
// internal use
now nowFn
@@ -57,7 +69,9 @@ type Logger struct {
lock sync.Mutex
callerOn bool
levelBracesOn bool
errorDump bool
templ *template.Template
reTrace *regexp.Regexp
}
// can be redefined internally for testing
@@ -85,6 +99,8 @@ func New(options ...Option) *Logger {
stdout: os.Stdout,
stderr: os.Stderr,
callerDepth: 0,
mapper: nopMapper,
reTrace: reTraceDefault,
}
for _, opt := range options {
opt(&res)
@@ -124,9 +140,16 @@ func (l *Logger) Logf(format string, args ...interface{}) {
l.logf(format, args...)
}
//nolint gocyclo
func (l *Logger) logf(format string, args ...interface{}) {
lv, msg := l.extractLevel(fmt.Sprintf(format, args...))
var lv, msg string
if len(args) == 0 {
lv, msg = l.extractLevel(format)
} else {
lv, msg = l.extractLevel(fmt.Sprintf(format, args...))
}
if lv == "DEBUG" && !l.dbg {
return
}
@@ -177,6 +200,15 @@ func (l *Logger) logf(format string, args ...interface{}) {
if l.stderr != l.stdout {
_, _ = l.stderr.Write(data)
}
if l.errorDump {
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := l.reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) > 0 {
_, _ = l.stdout.Write([]byte(">>> stack trace:\n" + traceLines[len(traceLines)-1]))
}
}
}
case "FATAL":
if l.stderr != l.stdout {
_, _ = l.stderr.Write(data)
@@ -195,7 +227,7 @@ func (l *Logger) logf(format string, args ...interface{}) {
func (l *Logger) hideSecrets(data []byte) []byte {
for _, h := range l.secrets {
data = bytes.Replace(data, []byte(h), secretReplacement, -1)
data = bytes.Replace(data, h, secretReplacement, -1)
}
return data
}
@@ -268,15 +300,17 @@ func (l *Logger) formatWithOptions(elems layout) (res string) {
parts := make([]string, 0, 4)
parts = append(parts, orElse(l.msec,
func() string { return elems.DT.Format("2006/01/02 15:04:05.000") },
func() string { return elems.DT.Format("2006/01/02 15:04:05") },
))
parts = append(parts, orElse(l.levelBraces,
func() string { return `[` + elems.Level + `]` },
func() string { return elems.Level },
))
parts = append(
parts,
l.mapper.TimeFunc(orElse(l.msec,
func() string { return elems.DT.Format("2006/01/02 15:04:05.000") },
func() string { return elems.DT.Format("2006/01/02 15:04:05") },
)),
l.levelMapper(elems.Level)(orElse(l.levelBraces,
func() string { return `[` + elems.Level + `]` },
func() string { return elems.Level },
)),
)
if l.callerFile || l.callerFunc || l.callerPkg {
var callerParts []string
@@ -290,10 +324,20 @@ func (l *Logger) formatWithOptions(elems layout) (res string) {
if v := orElse(l.callerPkg, func() string { return elems.CallerPkg }, nothing); v != "" {
callerParts = append(callerParts, v)
}
parts = append(parts, "{"+strings.Join(callerParts, " ")+"}")
caller := "{" + strings.Join(callerParts, " ") + "}"
if l.mapper.CallerFunc != nil {
caller = l.mapper.CallerFunc(caller)
}
parts = append(parts, caller)
}
parts = append(parts, elems.Message)
msg := elems.Message
if l.mapper.MessageFunc != nil {
msg = l.mapper.MessageFunc(elems.Message)
}
parts = append(parts, l.levelMapper(elems.Level)(msg))
return strings.Join(parts, " ")
}
@@ -320,6 +364,37 @@ func (l *Logger) extractLevel(line string) (level, msg string) {
return "INFO", line
}
func (l *Logger) levelMapper(level string) mapFunc {
nop := func(s string) string {
return s
}
switch level {
case "TRACE", "DEBUG":
if l.mapper.DebugFunc == nil {
return nop
}
return l.mapper.DebugFunc
case "INFO ":
if l.mapper.InfoFunc == nil {
return nop
}
return l.mapper.InfoFunc
case "WARN ":
if l.mapper.WarnFunc == nil {
return nop
}
return l.mapper.WarnFunc
case "ERROR", "PANIC", "FATAL":
if l.mapper.ErrorFunc == nil {
return nop
}
return l.mapper.ErrorFunc
}
return func(s string) string { return s }
}
// getDump reads runtime stack and returns as a string
func getDump() []byte {
maxSize := 5 * 1024 * 1024
+28
View File
@@ -0,0 +1,28 @@
package lgr
// Mapper defines optional functions to change elements of the logged message for each part, based on levels.
// Only some mapFunc can be defined, by default does nothing. Can be used to alter the output, for example making some
// part of the output colorful.
type Mapper struct {
MessageFunc mapFunc // message mapper on all levels
ErrorFunc mapFunc // message mapper on ERROR level
WarnFunc mapFunc // message mapper on WARN level
InfoFunc mapFunc // message mapper on INFO level
DebugFunc mapFunc // message mapper on DEBUG level
CallerFunc mapFunc // caller mapper, all levels
TimeFunc mapFunc // time mapper, all levels
}
type mapFunc func(string) string
// nopMapper is a default, doing nothing
var nopMapper = Mapper{
MessageFunc: func(s string) string { return s },
ErrorFunc: func(s string) string { return s },
WarnFunc: func(s string) string { return s },
InfoFunc: func(s string) string { return s },
DebugFunc: func(s string) string { return s },
CallerFunc: func(s string) string { return s },
TimeFunc: func(s string) string { return s },
}
+15 -1
View File
@@ -73,6 +73,20 @@ func Msec(l *Logger) {
// Useful to prevent passwords or other sensitive tokens to be logged.
func Secret(vals ...string) Option {
return func(l *Logger) {
l.secrets = vals
for _, v := range vals {
l.secrets = append(l.secrets, []byte(v))
}
}
}
// Map sets mapper functions to change elements of the logged message based on levels.
func Map(m Mapper) Option {
return func(l *Logger) {
l.mapper = m
}
}
// StackTraceOnError turns on stack trace for ERROR level.
func StackTraceOnError(l *Logger) {
l.errorDump = true
}
-2
View File
@@ -1,12 +1,10 @@
dist: xenial
sudo: false
language: go
services:
- redis-server
go:
- 1.11.x
- 1.12.x
- 1.13.x
- tip
+4
View File
@@ -4,6 +4,10 @@
- Existing `HMSet` is renamed to `HSet` and old deprecated `HMSet` is restored for Redis 3 users.
## v7.1
- Existing `Cmd.String` is renamed to `Cmd.Text`. New `Cmd.String` implements `fmt.Stringer` interface.
## v7
- *Important*. Tx.Pipeline now returns a non-transactional pipeline. Use Tx.TxPipeline for a transactional pipeline.
+1 -1
View File
@@ -14,7 +14,7 @@ bench: testdeps
testdata/redis:
mkdir -p $@
wget -qO- http://download.redis.io/releases/redis-5.0.7.tar.gz | tar xvz --strip-components=1 -C $@
wget -qO- http://download.redis.io/redis-stable.tar.gz | tar xvz --strip-components=1 -C $@
testdata/redis/src/redis-server: testdata/redis
cd $< && make all
+5 -2
View File
@@ -101,7 +101,7 @@ set, err := client.SetNX("key", "value", 10*time.Second).Result()
vals, err := client.Sort("list", &redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result()
// ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2
vals, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{
vals, err := client.ZRangeByScoreWithScores("zset", &redis.ZRangeBy{
Min: "-inf",
Max: "+inf",
Offset: 0,
@@ -109,7 +109,10 @@ vals, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{
}).Result()
// ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM
vals, err := client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2").Result()
vals, err := client.ZInterStore("out", &redis.ZStore{
Keys: []string{"zset1", "zset2"},
Weights: []int64{2, 3}
}).Result()
// EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello"
vals, err := client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result()
+10 -1
View File
@@ -57,6 +57,7 @@ type ClusterOptions struct {
OnConnect func(*Conn) error
Username string
Password string
MaxRetries int
@@ -67,6 +68,9 @@ type ClusterOptions struct {
ReadTimeout time.Duration
WriteTimeout time.Duration
// NewClient creates a cluster node client with provided name and options.
NewClient func(opt *Options) *Client
// PoolSize applies per cluster node and not for the whole cluster.
PoolSize int
MinIdleConns int
@@ -118,6 +122,10 @@ func (opt *ClusterOptions) init() {
case 0:
opt.MaxRetryBackoff = 512 * time.Millisecond
}
if opt.NewClient == nil {
opt.NewClient = NewClient
}
}
func (opt *ClusterOptions) clientOptions() *Options {
@@ -130,6 +138,7 @@ func (opt *ClusterOptions) clientOptions() *Options {
MaxRetries: opt.MaxRetries,
MinRetryBackoff: opt.MinRetryBackoff,
MaxRetryBackoff: opt.MaxRetryBackoff,
Username: opt.Username,
Password: opt.Password,
readOnly: opt.ReadOnly,
@@ -162,7 +171,7 @@ func newClusterNode(clOpt *ClusterOptions, addr string) *clusterNode {
opt := clOpt.clientOptions()
opt.Addr = addr
node := clusterNode{
Client: NewClient(opt),
Client: clOpt.NewClient(opt),
}
node.latency = math.MaxUint32
+32 -3
View File
@@ -671,7 +671,7 @@ func (cmd *StringCmd) Time() (time.Time, error) {
if cmd.err != nil {
return time.Time{}, cmd.err
}
return time.Parse(time.RFC3339, cmd.Val())
return time.Parse(time.RFC3339Nano, cmd.Val())
}
func (cmd *StringCmd) Scan(val interface{}) error {
@@ -1885,6 +1885,7 @@ type CommandInfo struct {
Name string
Arity int8
Flags []string
ACLFlags []string
FirstKeyPos int8
LastKeyPos int8
StepCount int8
@@ -1934,8 +1935,14 @@ func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error {
}
func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) {
if n != 6 {
return nil, fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 6", n)
const numArgRedis5 = 6
const numArgRedis6 = 7
switch n {
case numArgRedis5, numArgRedis6:
// continue
default:
return nil, fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 7", n)
}
var cmd CommandInfo
@@ -1995,6 +2002,28 @@ func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) {
}
}
if n == numArgRedis5 {
return &cmd, nil
}
_, err = rd.ReadReply(func(rd *proto.Reader, n int64) (interface{}, error) {
cmd.ACLFlags = make([]string, n)
for i := 0; i < len(cmd.ACLFlags); i++ {
switch s, err := rd.ReadString(); {
case err == Nil:
cmd.ACLFlags[i] = ""
case err != nil:
return nil, err
default:
cmd.ACLFlags[i] = s
}
}
return nil, nil
})
if err != nil {
return nil, err
}
return &cmd, nil
}
+10
View File
@@ -302,6 +302,7 @@ type Cmdable interface {
type StatefulCmdable interface {
Cmdable
Auth(password string) *StatusCmd
AuthACL(username, password string) *StatusCmd
Select(index int) *StatusCmd
SwapDB(index1, index2 int) *StatusCmd
ClientSetName(name string) *BoolCmd
@@ -324,6 +325,15 @@ func (c statefulCmdable) Auth(password string) *StatusCmd {
return cmd
}
// Perform an AUTH command, using the given user and pass.
// Should be used to authenticate the current connection with one of the connections defined in the ACL list
// when connecting to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
func (c statefulCmdable) AuthACL(username, password string) *StatusCmd {
cmd := NewStatusCmd("auth", username, password)
_ = c(cmd)
return cmd
}
func (c cmdable) Echo(message interface{}) *StringCmd {
cmd := NewStringCmd("echo", message)
_ = c(cmd)
+15
View File
@@ -6,9 +6,24 @@ import (
"net"
"strings"
"github.com/go-redis/redis/v7/internal/pool"
"github.com/go-redis/redis/v7/internal/proto"
)
var ErrClosed = pool.ErrClosed
type Error interface {
error
// RedisError is a no-op function but
// serves to distinguish types that are Redis
// errors from ordinary errors: a type is a
// Redis error if it has a RedisError method.
RedisError()
}
var _ Error = proto.RedisError("")
func isRetryableError(err error, retryTimeout bool) bool {
switch err {
case nil, context.Canceled, context.DeadlineExceeded:
+2
View File
@@ -94,7 +94,9 @@ func NewConnPool(opt *Options) *ConnPool {
closedCh: make(chan struct{}),
}
p.connsMu.Lock()
p.checkMinIdleConns()
p.connsMu.Unlock()
if opt.IdleTimeout > 0 && opt.IdleCheckFrequency > 0 {
go p.reaper(opt.IdleCheckFrequency)
+2
View File
@@ -24,6 +24,8 @@ type RedisError string
func (e RedisError) Error() string { return string(e) }
func (RedisError) RedisError() {}
//------------------------------------------------------------------------------
type MultiBulkParse func(*Reader, int64) (interface{}, error)
+1 -1
View File
@@ -93,7 +93,7 @@ func (w *Writer) writeArg(v interface{}) error {
}
return w.int(0)
case time.Time:
return w.string(v.Format(time.RFC3339))
return w.string(v.Format(time.RFC3339Nano))
case encoding.BinaryMarshaler:
b, err := v.MarshalBinary()
if err != nil {
+7 -1
View File
@@ -40,8 +40,13 @@ type Options struct {
// Hook that is called when new connection is established.
OnConnect func(*Conn) error
// Use the specified Username to authenticate the current connection with one of the connections defined in the ACL
// list when connecting to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
Username string
// Optional password. Must match the password specified in the
// requirepass server configuration option.
// requirepass server configuration option (if connecting to a Redis 5.0 instance, or lower),
// or the User Password when connecting to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
Password string
// Database to be selected after connecting to the server.
DB int
@@ -187,6 +192,7 @@ func ParseURL(redisURL string) (*Options, error) {
}
if u.User != nil {
o.Username = u.User.Username()
if p, ok := u.User.Password(); ok {
o.Password = p
}
+3 -1
View File
@@ -309,9 +309,11 @@ func (c *PubSub) newMessage(reply interface{}) (interface{}, error) {
case []interface{}:
switch kind := reply[0].(string); kind {
case "subscribe", "unsubscribe", "psubscribe", "punsubscribe":
// Can be nil in case of "unsubscribe".
channel, _ := reply[1].(string)
return &Subscription{
Kind: kind,
Channel: reply[1].(string),
Channel: channel,
Count: int(reply[2].(int64)),
}, nil
case "message":
+5 -1
View File
@@ -241,7 +241,11 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
_, err := conn.Pipelined(func(pipe Pipeliner) error {
if c.opt.Password != "" {
pipe.Auth(c.opt.Password)
if c.opt.Username != "" {
pipe.AuthACL(c.opt.Username, c.opt.Password)
} else {
pipe.Auth(c.opt.Password)
}
}
if c.opt.DB > 0 {
+8
View File
@@ -98,6 +98,14 @@ func NewStringIntMapCmdResult(val map[string]int64, err error) *StringIntMapCmd
return &cmd
}
// NewTimeCmdResult returns a TimeCmd initialised with val and err for testing
func NewTimeCmdResult(val time.Time, err error) *TimeCmd {
var cmd TimeCmd
cmd.val = val
cmd.SetErr(err)
return &cmd
}
// NewZSliceCmdResult returns a ZSliceCmd initialised with val and err for testing
func NewZSliceCmdResult(val []Z, err error) *ZSliceCmd {
var cmd ZSliceCmd
+9 -1
View File
@@ -56,6 +56,9 @@ type RingOptions struct {
// See https://arxiv.org/abs/1406.2294 for reference
HashReplicas int
// NewClient creates a shard client with provided name and options.
NewClient func(name string, opt *Options) *Client
// Optional hook that is called when a new shard is created.
OnNewShard func(*Client)
@@ -390,7 +393,12 @@ func NewRing(opt *RingOptions) *Ring {
func newRingShard(opt *RingOptions, name, addr string) *Client {
clopt := opt.clientOptions(name)
clopt.Addr = addr
shard := NewClient(clopt)
var shard *Client
if opt.NewClient != nil {
shard = opt.NewClient(name, clopt)
} else {
shard = NewClient(clopt)
}
if opt.OnNewShard != nil {
opt.OnNewShard(shard)
}
+6
View File
@@ -22,6 +22,7 @@ type FailoverOptions struct {
MasterName string
// A seed list of host:port addresses of sentinel nodes.
SentinelAddrs []string
SentinelUsername string
SentinelPassword string
// Following options are copied from Options struct.
@@ -29,6 +30,7 @@ type FailoverOptions struct {
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
OnConnect func(*Conn) error
Username string
Password string
DB int
@@ -57,6 +59,7 @@ func (opt *FailoverOptions) options() *Options {
OnConnect: opt.OnConnect,
DB: opt.DB,
Username: opt.Username,
Password: opt.Password,
MaxRetries: opt.MaxRetries,
@@ -88,6 +91,7 @@ func NewFailoverClient(failoverOpt *FailoverOptions) *Client {
failover := &sentinelFailover{
masterName: failoverOpt.MasterName,
sentinelAddrs: failoverOpt.SentinelAddrs,
username: failoverOpt.SentinelUsername,
password: failoverOpt.SentinelPassword,
opt: opt,
@@ -281,6 +285,7 @@ type sentinelFailover struct {
sentinelAddrs []string
opt *Options
username string
password string
pool *pool.ConnPool
@@ -372,6 +377,7 @@ func (c *sentinelFailover) masterAddr() (string, error) {
Addr: sentinelAddr,
Dialer: c.opt.Dialer,
Username: c.username,
Password: c.password,
MaxRetries: c.opt.MaxRetries,
+4
View File
@@ -22,6 +22,7 @@ type UniversalOptions struct {
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
OnConnect func(*Conn) error
Username string
Password string
MaxRetries int
MinRetryBackoff time.Duration
@@ -60,6 +61,7 @@ func (o *UniversalOptions) Cluster() *ClusterOptions {
Dialer: o.Dialer,
OnConnect: o.OnConnect,
Username: o.Username,
Password: o.Password,
MaxRedirects: o.MaxRedirects,
@@ -99,6 +101,7 @@ func (o *UniversalOptions) Failover() *FailoverOptions {
OnConnect: o.OnConnect,
DB: o.DB,
Username: o.Username,
Password: o.Password,
MaxRetries: o.MaxRetries,
@@ -133,6 +136,7 @@ func (o *UniversalOptions) Simple() *Options {
OnConnect: o.OnConnect,
DB: o.DB,
Username: o.Username,
Password: o.Password,
MaxRetries: o.MaxRetries,
+1 -1
View File
@@ -16,4 +16,4 @@ change is the ability to represent an invalid UUID (vs a NIL UUID).
Full `go doc` style documentation for the package can be viewed online without
installing this package by using the GoDoc site here:
http://godoc.org/github.com/google/uuid
http://pkg.go.dev/github.com/google/uuid
+4 -3
View File
@@ -16,10 +16,11 @@ func (uuid UUID) MarshalText() ([]byte, error) {
// UnmarshalText implements encoding.TextUnmarshaler.
func (uuid *UUID) UnmarshalText(data []byte) error {
id, err := ParseBytes(data)
if err == nil {
*uuid = id
if err != nil {
return err
}
return err
*uuid = id
return nil
}
// MarshalBinary implements encoding.BinaryMarshaler.
+6 -6
View File
@@ -17,12 +17,6 @@ import (
//
// In most cases, New should be used.
func NewUUID() (UUID, error) {
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
nodeMu.Unlock()
var uuid UUID
now, seq, err := GetTime()
if err != nil {
@@ -38,7 +32,13 @@ func NewUUID() (UUID, error) {
binary.BigEndian.PutUint16(uuid[4:], timeMid)
binary.BigEndian.PutUint16(uuid[6:], timeHi)
binary.BigEndian.PutUint16(uuid[8:], seq)
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
copy(uuid[10:], nodeID[:])
nodeMu.Unlock()
return uuid, nil
}
+6 -1
View File
@@ -27,8 +27,13 @@ func New() UUID {
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() (UUID, error) {
return NewRandomFromReader(rander)
}
// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader.
func NewRandomFromReader(r io.Reader) (UUID, error) {
var uuid UUID
_, err := io.ReadFull(rander, uuid[:])
_, err := io.ReadFull(r, uuid[:])
if err != nil {
return Nil, err
}
+67 -23
View File
@@ -1,4 +1,6 @@
Blackfriday [![Build Status](https://travis-ci.org/russross/blackfriday.svg?branch=master)](https://travis-ci.org/russross/blackfriday)
Blackfriday
[![Build Status][BuildV2SVG]][BuildV2URL]
[![PkgGoDev][PkgGoDevV2SVG]][PkgGoDevV2URL]
===========
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
@@ -16,19 +18,21 @@ It started as a translation from C of [Sundown][3].
Installation
------------
Blackfriday is compatible with any modern Go release. With Go 1.7 and git
installed:
Blackfriday is compatible with modern Go releases in module mode.
With Go installed:
go get gopkg.in/russross/blackfriday.v2
go get github.com/russross/blackfriday/v2
will download, compile, and install the package into your `$GOPATH`
directory hierarchy. Alternatively, you can achieve the same if you
import it into a project:
will resolve and add the package to the current development module,
then build and install it. Alternatively, you can achieve the same
if you import it in a package:
import "gopkg.in/russross/blackfriday.v2"
import "github.com/russross/blackfriday/v2"
and `go get` without parameters.
Legacy GOPATH mode is unsupported.
Versions
--------
@@ -36,13 +40,9 @@ Versions
Currently maintained and recommended version of Blackfriday is `v2`. It's being
developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
documentation is available at
https://godoc.org/gopkg.in/russross/blackfriday.v2.
https://pkg.go.dev/github.com/russross/blackfriday/v2.
It is `go get`-able via via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`,
but we highly recommend using package management tool like [dep][7] or
[Glide][8] and make use of semantic versioning. With package management you
should import `github.com/russross/blackfriday` and specify that you're using
version 2.0.0.
It is `go get`-able in module mode at `github.com/russross/blackfriday/v2`.
Version 2 offers a number of improvements over v1:
@@ -62,6 +62,11 @@ Potential drawbacks:
v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for
tracking.
If you are still interested in the legacy `v1`, you can import it from
`github.com/russross/blackfriday`. Documentation for the legacy v1 can be found
here: https://pkg.go.dev/github.com/russross/blackfriday.
Usage
-----
@@ -91,7 +96,7 @@ Here's an example of simple usage of Blackfriday together with Bluemonday:
```go
import (
"github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday"
"github.com/russross/blackfriday/v2"
)
// ...
@@ -104,6 +109,8 @@ html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
If you want to customize the set of options, use `blackfriday.WithExtensions`,
`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
### `blackfriday-tool`
You can also check out `blackfriday-tool` for a more complete example
of how to use it. Download and install it using:
@@ -114,7 +121,7 @@ markdown file using a standalone program. You can also browse the
source directly on github if you are just looking for some example
code:
* <http://github.com/russross/blackfriday-tool>
* <https://github.com/russross/blackfriday-tool>
Note that if you have not already done so, installing
`blackfriday-tool` will be sufficient to download and install
@@ -123,6 +130,22 @@ installed in `$GOPATH/bin`. This is a statically-linked binary that
can be copied to wherever you need it without worrying about
dependencies and library versions.
### Sanitized anchor names
Blackfriday includes an algorithm for creating sanitized anchor names
corresponding to a given input text. This algorithm is used to create
anchors for headings when `AutoHeadingIDs` extension is enabled. The
algorithm has a specification, so that other packages can create
compatible anchor names and links to those anchors.
The specification is located at https://pkg.go.dev/github.com/russross/blackfriday/v2#hdr-Sanitized_Anchor_Names.
[`SanitizedAnchorName`](https://pkg.go.dev/github.com/russross/blackfriday/v2#SanitizedAnchorName) exposes this functionality, and can be used to
create compatible links to the anchor names generated by blackfriday.
This algorithm is also implemented in a small standalone package at
[`github.com/shurcooL/sanitized_anchor_name`](https://pkg.go.dev/github.com/shurcooL/sanitized_anchor_name). It can be useful for clients
that want a small package and don't need full functionality of blackfriday.
Features
--------
@@ -199,6 +222,15 @@ implements the following extensions:
You can use 3 or more backticks to mark the beginning of the
block, and the same number to mark the end of the block.
To preserve classes of fenced code blocks while using the bluemonday
HTML sanitizer, use the following policy:
```go
p := bluemonday.UGCPolicy()
p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
html := p.SanitizeBytes(unsafe)
```
* **Definition lists**. A simple definition list is made of a single-line
term followed by a colon and the definition for that term.
@@ -250,7 +282,7 @@ Other renderers
Blackfriday is structured to allow alternative rendering engines. Here
are a few of note:
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
* [github_flavored_markdown](https://pkg.go.dev/github.com/shurcooL/github_flavored_markdown):
provides a GitHub Flavored Markdown renderer with fenced code block
highlighting, clickable heading anchor links.
@@ -261,20 +293,28 @@ are a few of note:
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
but for markdown.
* [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX):
* [LaTeX output](https://gitlab.com/ambrevar/blackfriday-latex):
renders output as LaTeX.
* [bfchroma](https://github.com/Depado/bfchroma/): provides convenience
integration with the [Chroma](https://github.com/alecthomas/chroma) code
highlighting library. bfchroma is only compatible with v2 of Blackfriday and
provides a drop-in renderer ready to use with Blackfriday, as well as
options and means for further customization.
* [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer.
* [Blackfriday-Slack](https://github.com/karriereat/blackfriday-slack): converts markdown to slack message style
Todo
TODO
----
* More unit testing
* Improve unicode support. It does not understand all unicode
* Improve Unicode support. It does not understand all Unicode
rules (about what constitutes a letter, a punctuation symbol,
etc.), so it may fail to detect word boundaries correctly in
some instances. It is safe on all utf-8 input.
some instances. It is safe on all UTF-8 input.
License
@@ -286,6 +326,10 @@ License
[1]: https://daringfireball.net/projects/markdown/ "Markdown"
[2]: https://golang.org/ "Go Language"
[3]: https://github.com/vmg/sundown "Sundown"
[4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func"
[4]: https://pkg.go.dev/github.com/russross/blackfriday/v2#Parse "Parse func"
[5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
[6]: https://labix.org/gopkg.in "gopkg.in"
[BuildV2SVG]: https://travis-ci.org/russross/blackfriday.svg?branch=v2
[BuildV2URL]: https://travis-ci.org/russross/blackfriday
[PkgGoDevV2SVG]: https://pkg.go.dev/badge/github.com/russross/blackfriday/v2
[PkgGoDevV2URL]: https://pkg.go.dev/github.com/russross/blackfriday/v2
+26 -4
View File
@@ -18,8 +18,7 @@ import (
"html"
"regexp"
"strings"
"github.com/shurcooL/sanitized_anchor_name"
"unicode"
)
const (
@@ -259,7 +258,7 @@ func (p *Markdown) prefixHeading(data []byte) int {
}
if end > i {
if id == "" && p.extensions&AutoHeadingIDs != 0 {
id = sanitized_anchor_name.Create(string(data[i:end]))
id = SanitizedAnchorName(string(data[i:end]))
}
block := p.addBlock(Heading, data[i:end])
block.HeadingID = id
@@ -673,6 +672,7 @@ func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
if beg == 0 || beg >= len(data) {
return 0
}
fenceLength := beg - 1
var work bytes.Buffer
work.Write([]byte(info))
@@ -706,6 +706,7 @@ func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
if doRender {
block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer
block.IsFenced = true
block.FenceLength = fenceLength
finalizeCodeBlock(block)
}
@@ -1503,7 +1504,7 @@ func (p *Markdown) paragraph(data []byte) int {
id := ""
if p.extensions&AutoHeadingIDs != 0 {
id = sanitized_anchor_name.Create(string(data[prev:eol]))
id = SanitizedAnchorName(string(data[prev:eol]))
}
block := p.addBlock(Heading, data[prev:eol])
@@ -1588,3 +1589,24 @@ func skipUntilChar(text []byte, start int, char byte) int {
}
return i
}
// SanitizedAnchorName returns a sanitized anchor name for the given text.
//
// It implements the algorithm specified in the package comment.
func SanitizedAnchorName(text string) string {
var anchorName []rune
futureDash := false
for _, r := range text {
switch {
case unicode.IsLetter(r) || unicode.IsNumber(r):
if futureDash && len(anchorName) > 0 {
anchorName = append(anchorName, '-')
}
futureDash = false
anchorName = append(anchorName, unicode.ToLower(r))
default:
futureDash = true
}
}
return string(anchorName)
}
+28
View File
@@ -15,4 +15,32 @@
//
// If you're interested in calling Blackfriday from command line, see
// https://github.com/russross/blackfriday-tool.
//
// Sanitized Anchor Names
//
// Blackfriday includes an algorithm for creating sanitized anchor names
// corresponding to a given input text. This algorithm is used to create
// anchors for headings when AutoHeadingIDs extension is enabled. The
// algorithm is specified below, so that other packages can create
// compatible anchor names and links to those anchors.
//
// The algorithm iterates over the input text, interpreted as UTF-8,
// one Unicode code point (rune) at a time. All runes that are letters (category L)
// or numbers (category N) are considered valid characters. They are mapped to
// lower case, and included in the output. All other runes are considered
// invalid characters. Invalid characters that precede the first valid character,
// as well as invalid character that follow the last valid character
// are dropped completely. All other sequences of invalid characters
// between two valid characters are replaced with a single dash character '-'.
//
// SanitizedAnchorName exposes this functionality, and can be used to
// create compatible links to the anchor names generated by blackfriday.
// This algorithm is also implemented in a small standalone package at
// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients
// that want a small package and don't need full functionality of blackfriday.
package blackfriday
// NOTE: Keep Sanitized Anchor Name algorithm in sync with package
// github.com/shurcooL/sanitized_anchor_name.
// Otherwise, users of sanitized_anchor_name will get anchor names
// that are incompatible with those generated by blackfriday.
File diff suppressed because it is too large Load Diff
+39 -3
View File
@@ -13,13 +13,27 @@ var htmlEscaper = [256][]byte{
}
func escapeHTML(w io.Writer, s []byte) {
escapeEntities(w, s, false)
}
func escapeAllHTML(w io.Writer, s []byte) {
escapeEntities(w, s, true)
}
func escapeEntities(w io.Writer, s []byte, escapeValidEntities bool) {
var start, end int
for end < len(s) {
escSeq := htmlEscaper[s[end]]
if escSeq != nil {
w.Write(s[start:end])
w.Write(escSeq)
start = end + 1
isEntity, entityEnd := nodeIsEntity(s, end)
if isEntity && !escapeValidEntities {
w.Write(s[start : entityEnd+1])
start = entityEnd + 1
} else {
w.Write(s[start:end])
w.Write(escSeq)
start = end + 1
}
}
end++
}
@@ -28,6 +42,28 @@ func escapeHTML(w io.Writer, s []byte) {
}
}
func nodeIsEntity(s []byte, end int) (isEntity bool, endEntityPos int) {
isEntity = false
endEntityPos = end + 1
if s[end] == '&' {
for endEntityPos < len(s) {
if s[endEntityPos] == ';' {
if entities[string(s[end:endEntityPos+1])] {
isEntity = true
break
}
}
if !isalnum(s[endEntityPos]) && s[endEntityPos] != '&' && s[endEntityPos] != '#' {
break
}
endEntityPos++
}
}
return isEntity, endEntityPos
}
func escLink(w io.Writer, text []byte) {
unesc := html.UnescapeString(string(text))
escapeHTML(w, []byte(unesc))
+6 -3
View File
@@ -132,7 +132,10 @@ func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
}
if params.FootnoteReturnLinkContents == "" {
params.FootnoteReturnLinkContents = `<sup>[return]</sup>`
// U+FE0E is VARIATION SELECTOR-15.
// It suppresses automatic emoji presentation of the preceding
// U+21A9 LEFTWARDS ARROW WITH HOOK on iOS and iPadOS.
params.FootnoteReturnLinkContents = "<span aria-label='Return'>↩\ufe0e</span>"
}
return &HTMLRenderer{
@@ -616,7 +619,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
}
case Code:
r.out(w, codeTag)
escapeHTML(w, node.Literal)
escapeAllHTML(w, node.Literal)
r.out(w, codeCloseTag)
case Document:
break
@@ -762,7 +765,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
r.cr(w)
r.out(w, preTag)
r.tag(w, codeTag[:len(codeTag)-1], attrs)
escapeHTML(w, node.Literal)
escapeAllHTML(w, node.Literal)
r.out(w, codeCloseTag)
r.out(w, preCloseTag)
if node.Parent.Type != Item {
+1 -1
View File
@@ -278,7 +278,7 @@ func link(p *Markdown, data []byte, offset int) (int, *Node) {
case data[i] == '\n':
textHasNl = true
case data[i-1] == '\\':
case isBackslashEscaped(data, i):
continue
case data[i] == '[':
+9 -3
View File
@@ -199,7 +199,8 @@ func (n *Node) InsertBefore(sibling *Node) {
}
}
func (n *Node) isContainer() bool {
// IsContainer returns true if 'n' can contain children.
func (n *Node) IsContainer() bool {
switch n.Type {
case Document:
fallthrough
@@ -238,6 +239,11 @@ func (n *Node) isContainer() bool {
}
}
// IsLeaf returns true if 'n' is a leaf node.
func (n *Node) IsLeaf() bool {
return !n.IsContainer()
}
func (n *Node) canContain(t NodeType) bool {
if n.Type == List {
return t == Item
@@ -309,11 +315,11 @@ func newNodeWalker(root *Node) *nodeWalker {
}
func (nw *nodeWalker) next() {
if (!nw.current.isContainer() || !nw.entering) && nw.current == nw.root {
if (!nw.current.IsContainer() || !nw.entering) && nw.current == nw.root {
nw.current = nil
return
}
if nw.entering && nw.current.isContainer() {
if nw.entering && nw.current.IsContainer() {
if nw.current.FirstChild != nil {
nw.current = nw.current.FirstChild
nw.entering = true
-16
View File
@@ -1,16 +0,0 @@
sudo: false
language: go
go:
- 1.x
- master
matrix:
allow_failures:
- go: master
fast_finish: true
install:
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d -s .)
- go tool vet .
- go test -v -race ./...
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2015 Dmitri Shuralyov
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.
-36
View File
@@ -1,36 +0,0 @@
sanitized_anchor_name
=====================
[![Build Status](https://travis-ci.org/shurcooL/sanitized_anchor_name.svg?branch=master)](https://travis-ci.org/shurcooL/sanitized_anchor_name) [![GoDoc](https://godoc.org/github.com/shurcooL/sanitized_anchor_name?status.svg)](https://godoc.org/github.com/shurcooL/sanitized_anchor_name)
Package sanitized_anchor_name provides a func to create sanitized anchor names.
Its logic can be reused by multiple packages to create interoperable anchor names
and links to those anchors.
At this time, it does not try to ensure that generated anchor names
are unique, that responsibility falls on the caller.
Installation
------------
```bash
go get -u github.com/shurcooL/sanitized_anchor_name
```
Example
-------
```Go
anchorName := sanitized_anchor_name.Create("This is a header")
fmt.Println(anchorName)
// Output:
// this-is-a-header
```
License
-------
- [MIT License](LICENSE)
-1
View File
@@ -1 +0,0 @@
module github.com/shurcooL/sanitized_anchor_name
-29
View File
@@ -1,29 +0,0 @@
// Package sanitized_anchor_name provides a func to create sanitized anchor names.
//
// Its logic can be reused by multiple packages to create interoperable anchor names
// and links to those anchors.
//
// At this time, it does not try to ensure that generated anchor names
// are unique, that responsibility falls on the caller.
package sanitized_anchor_name // import "github.com/shurcooL/sanitized_anchor_name"
import "unicode"
// Create returns a sanitized anchor name for the given text.
func Create(text string) string {
var anchorName []rune
var futureDash = false
for _, r := range text {
switch {
case unicode.IsLetter(r) || unicode.IsNumber(r):
if futureDash && len(anchorName) > 0 {
anchorName = append(anchorName, '-')
}
futureDash = false
anchorName = append(anchorName, unicode.ToLower(r))
default:
futureDash = true
}
}
return string(anchorName)
}
+6 -5
View File
@@ -152,11 +152,12 @@ are not thread safe. To work with data in multiple goroutines you must start
a transaction for each one or use locking to ensure only one goroutine accesses
a transaction at a time. Creating transaction from the `DB` is thread safe.
Read-only transactions and read-write transactions should not depend on one
another and generally shouldn't be opened simultaneously in the same goroutine.
This can cause a deadlock as the read-write transaction needs to periodically
re-map the data file but it cannot do so while a read-only transaction is open.
Transactions should not depend on one another and generally shouldn't be opened
simultaneously in the same goroutine. This can cause a deadlock as the read-write
transaction needs to periodically re-map the data file but it cannot do so while
any read-only transaction is open. Even a nested read-only transaction can cause
a deadlock, as the child transaction can block the parent transaction from releasing
its resources.
#### Read-write transactions
+24 -33
View File
@@ -2,7 +2,6 @@ package bbolt
import (
"fmt"
"reflect"
"sort"
"unsafe"
)
@@ -94,24 +93,8 @@ func (f *freelist) pending_count() int {
return count
}
// copyallunsafe copies a list of all free ids and all pending ids in one sorted list.
// copyall copies a list of all free ids and all pending ids in one sorted list.
// f.count returns the minimum length required for dst.
func (f *freelist) copyallunsafe(dstptr unsafe.Pointer) { // dstptr is []pgid data pointer
m := make(pgids, 0, f.pending_count())
for _, txp := range f.pending {
m = append(m, txp.ids...)
}
sort.Sort(m)
fpgids := f.getFreePageIDs()
sz := len(fpgids) + len(m)
dst := *(*[]pgid)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(dstptr),
Len: sz,
Cap: sz,
}))
mergepgids(dst, fpgids, m)
}
func (f *freelist) copyall(dst []pgid) {
m := make(pgids, 0, f.pending_count())
for _, txp := range f.pending {
@@ -284,21 +267,23 @@ func (f *freelist) read(p *page) {
}
// If the page.count is at the max uint16 value (64k) then it's considered
// an overflow and the size of the freelist is stored as the first element.
var idx, count uintptr = 0, uintptr(p.count)
var idx, count = 0, int(p.count)
if count == 0xFFFF {
idx = 1
count = uintptr(*(*pgid)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p))))
c := *(*pgid)(unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)))
count = int(c)
if count < 0 {
panic(fmt.Sprintf("leading element count %d overflows int", c))
}
}
// Copy the list of page ids from the freelist.
if count == 0 {
f.ids = nil
} else {
ids := *(*[]pgid)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p) + idx*unsafe.Sizeof(pgid(0)),
Len: int(count),
Cap: int(count),
}))
var ids []pgid
data := unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p), unsafe.Sizeof(ids[0]), idx)
unsafeSlice(unsafe.Pointer(&ids), data, count)
// copy the ids, so we don't modify on the freelist page directly
idsCopy := make([]pgid, count)
@@ -331,16 +316,22 @@ func (f *freelist) write(p *page) error {
// The page.count can only hold up to 64k elements so if we overflow that
// number then we handle it by putting the size in the first element.
lenids := f.count()
if lenids == 0 {
p.count = uint16(lenids)
} else if lenids < 0xFFFF {
p.count = uint16(lenids)
f.copyallunsafe(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p)))
l := f.count()
if l == 0 {
p.count = uint16(l)
} else if l < 0xFFFF {
p.count = uint16(l)
var ids []pgid
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
unsafeSlice(unsafe.Pointer(&ids), data, l)
f.copyall(ids)
} else {
p.count = 0xFFFF
*(*pgid)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p))) = pgid(lenids)
f.copyallunsafe(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p) + unsafe.Sizeof(pgid(0))))
var ids []pgid
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
unsafeSlice(unsafe.Pointer(&ids), data, l+1)
ids[0] = pgid(l)
f.copyall(ids[1:])
}
return nil
+10 -15
View File
@@ -3,7 +3,6 @@ package bbolt
import (
"bytes"
"fmt"
"reflect"
"sort"
"unsafe"
)
@@ -208,36 +207,32 @@ func (n *node) write(p *page) {
}
// Loop over each item and write it to the page.
bp := uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p) + n.pageElementSize()*uintptr(len(n.inodes))
// off tracks the offset into p of the start of the next data.
off := unsafe.Sizeof(*p) + n.pageElementSize()*uintptr(len(n.inodes))
for i, item := range n.inodes {
_assert(len(item.key) > 0, "write: zero-length inode key")
// Create a slice to write into of needed size and advance
// byte pointer for next iteration.
sz := len(item.key) + len(item.value)
b := unsafeByteSlice(unsafe.Pointer(p), off, 0, sz)
off += uintptr(sz)
// Write the page element.
if n.isLeaf {
elem := p.leafPageElement(uint16(i))
elem.pos = uint32(bp - uintptr(unsafe.Pointer(elem)))
elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem)))
elem.flags = item.flags
elem.ksize = uint32(len(item.key))
elem.vsize = uint32(len(item.value))
} else {
elem := p.branchPageElement(uint16(i))
elem.pos = uint32(bp - uintptr(unsafe.Pointer(elem)))
elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem)))
elem.ksize = uint32(len(item.key))
elem.pgid = item.pgid
_assert(elem.pgid != p.id, "write: circular dependency occurred")
}
// Create a slice to write into of needed size and advance
// byte pointer for next iteration.
klen, vlen := len(item.key), len(item.value)
sz := klen + vlen
b := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: bp,
Len: sz,
Cap: sz,
}))
bp += uintptr(sz)
// Write data for the element to the end of the page.
l := copy(b, item.key)
copy(b[l:], item.value)
+21 -36
View File
@@ -3,7 +3,6 @@ package bbolt
import (
"fmt"
"os"
"reflect"
"sort"
"unsafe"
)
@@ -51,13 +50,13 @@ func (p *page) typ() string {
// meta returns a pointer to the metadata section of the page.
func (p *page) meta() *meta {
return (*meta)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p)))
return (*meta)(unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)))
}
// leafPageElement retrieves the leaf node by index
func (p *page) leafPageElement(index uint16) *leafPageElement {
off := uintptr(index) * unsafe.Sizeof(leafPageElement{})
return (*leafPageElement)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p) + off))
return (*leafPageElement)(unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p),
leafPageElementSize, int(index)))
}
// leafPageElements retrieves a list of leaf nodes.
@@ -65,17 +64,16 @@ func (p *page) leafPageElements() []leafPageElement {
if p.count == 0 {
return nil
}
return *(*[]leafPageElement)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p),
Len: int(p.count),
Cap: int(p.count),
}))
var elems []leafPageElement
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
unsafeSlice(unsafe.Pointer(&elems), data, int(p.count))
return elems
}
// branchPageElement retrieves the branch node by index
func (p *page) branchPageElement(index uint16) *branchPageElement {
off := uintptr(index) * unsafe.Sizeof(branchPageElement{})
return (*branchPageElement)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p) + off))
return (*branchPageElement)(unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p),
unsafe.Sizeof(branchPageElement{}), int(index)))
}
// branchPageElements retrieves a list of branch nodes.
@@ -83,20 +81,15 @@ func (p *page) branchPageElements() []branchPageElement {
if p.count == 0 {
return nil
}
return *(*[]branchPageElement)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p),
Len: int(p.count),
Cap: int(p.count),
}))
var elems []branchPageElement
data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
unsafeSlice(unsafe.Pointer(&elems), data, int(p.count))
return elems
}
// dump writes n bytes of the page to STDERR as hex output.
func (p *page) hexdump(n int) {
buf := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(p)),
Len: n,
Cap: n,
}))
buf := unsafeByteSlice(unsafe.Pointer(p), 0, 0, n)
fmt.Fprintf(os.Stderr, "%x\n", buf)
}
@@ -115,11 +108,7 @@ type branchPageElement struct {
// key returns a byte slice of the node key.
func (n *branchPageElement) key() []byte {
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(n)) + uintptr(n.pos),
Len: int(n.ksize),
Cap: int(n.ksize),
}))
return unsafeByteSlice(unsafe.Pointer(n), 0, int(n.pos), int(n.pos)+int(n.ksize))
}
// leafPageElement represents a node on a leaf page.
@@ -132,20 +121,16 @@ type leafPageElement struct {
// key returns a byte slice of the node key.
func (n *leafPageElement) key() []byte {
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(n)) + uintptr(n.pos),
Len: int(n.ksize),
Cap: int(n.ksize),
}))
i := int(n.pos)
j := i + int(n.ksize)
return unsafeByteSlice(unsafe.Pointer(n), 0, i, j)
}
// value returns a byte slice of the node value.
func (n *leafPageElement) value() []byte {
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(n)) + uintptr(n.pos) + uintptr(n.ksize),
Len: int(n.vsize),
Cap: int(n.vsize),
}))
i := int(n.pos) + int(n.ksize)
j := i + int(n.vsize)
return unsafeByteSlice(unsafe.Pointer(n), 0, i, j)
}
// PageInfo represents human readable information about a page.
+8 -19
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"io"
"os"
"reflect"
"sort"
"strings"
"time"
@@ -524,24 +523,18 @@ func (tx *Tx) write() error {
// Write pages to disk in order.
for _, p := range pages {
size := (int(p.overflow) + 1) * tx.db.pageSize
rem := (uint64(p.overflow) + 1) * uint64(tx.db.pageSize)
offset := int64(p.id) * int64(tx.db.pageSize)
var written uintptr
// Write out page in "max allocation" sized chunks.
ptr := uintptr(unsafe.Pointer(p))
for {
// Limit our write to our max allocation size.
sz := size
sz := rem
if sz > maxAllocSize-1 {
sz = maxAllocSize - 1
}
buf := unsafeByteSlice(unsafe.Pointer(p), written, 0, int(sz))
// Write chunk to disk.
buf := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: ptr,
Len: sz,
Cap: sz,
}))
if _, err := tx.db.ops.writeAt(buf, offset); err != nil {
return err
}
@@ -550,14 +543,14 @@ func (tx *Tx) write() error {
tx.stats.Write++
// Exit inner for loop if we've written all the chunks.
size -= sz
if size == 0 {
rem -= sz
if rem == 0 {
break
}
// Otherwise move offset forward and move pointer to next chunk.
offset += int64(sz)
ptr += uintptr(sz)
written += uintptr(sz)
}
}
@@ -576,11 +569,7 @@ func (tx *Tx) write() error {
continue
}
buf := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(p)),
Len: tx.db.pageSize,
Cap: tx.db.pageSize,
}))
buf := unsafeByteSlice(unsafe.Pointer(p), 0, 0, tx.db.pageSize)
// See https://go.googlesource.com/go/+/f03c9202c43e0abb130669852082117ca50aa9b1
for i := range buf {
+39
View File
@@ -0,0 +1,39 @@
package bbolt
import (
"reflect"
"unsafe"
)
func unsafeAdd(base unsafe.Pointer, offset uintptr) unsafe.Pointer {
return unsafe.Pointer(uintptr(base) + offset)
}
func unsafeIndex(base unsafe.Pointer, offset uintptr, elemsz uintptr, n int) unsafe.Pointer {
return unsafe.Pointer(uintptr(base) + offset + uintptr(n)*elemsz)
}
func unsafeByteSlice(base unsafe.Pointer, offset uintptr, i, j int) []byte {
// See: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
//
// This memory is not allocated from C, but it is unmanaged by Go's
// garbage collector and should behave similarly, and the compiler
// should produce similar code. Note that this conversion allows a
// subslice to begin after the base address, with an optional offset,
// while the URL above does not cover this case and only slices from
// index 0. However, the wiki never says that the address must be to
// the beginning of a C allocation (or even that malloc was used at
// all), so this is believed to be correct.
return (*[maxAllocSize]byte)(unsafeAdd(base, offset))[i:j:j]
}
// unsafeSlice modifies the data, len, and cap of a slice variable pointed to by
// the slice parameter. This helper should be used over other direct
// manipulation of reflect.SliceHeader to prevent misuse, namely, converting
// from reflect.SliceHeader to a Go slice type.
func unsafeSlice(slice, data unsafe.Pointer, len int) {
s := (*reflect.SliceHeader)(slice)
s.Data = uintptr(data)
s.Cap = len
s.Len = len
}
+6 -8
View File
@@ -104,12 +104,12 @@ github.com/go-pkgz/expirable-cache
# github.com/go-pkgz/jrpc v0.2.0
## explicit
github.com/go-pkgz/jrpc
# github.com/go-pkgz/lcw v0.7.1
# github.com/go-pkgz/lcw v0.8.1
## explicit
github.com/go-pkgz/lcw
github.com/go-pkgz/lcw/eventbus
github.com/go-pkgz/lcw/internal/cache
# github.com/go-pkgz/lgr v0.7.0
# github.com/go-pkgz/lgr v0.10.4
## explicit
github.com/go-pkgz/lgr
# github.com/go-pkgz/repeater v1.1.3
@@ -123,7 +123,7 @@ github.com/go-pkgz/rest/logger
# github.com/go-pkgz/syncs v1.1.1
## explicit
github.com/go-pkgz/syncs
# github.com/go-redis/redis/v7 v7.2.0
# github.com/go-redis/redis/v7 v7.4.0
github.com/go-redis/redis/v7
github.com/go-redis/redis/v7/internal
github.com/go-redis/redis/v7/internal/consistenthash
@@ -137,7 +137,7 @@ github.com/go-stack/stack
github.com/golang/protobuf/proto
# github.com/golang/snappy v0.0.1
github.com/golang/snappy
# github.com/google/uuid v1.1.1
# github.com/google/uuid v1.1.2
## explicit
github.com/google/uuid
# github.com/gorilla/feeds v1.1.1
@@ -178,11 +178,9 @@ github.com/rakyll/statik/fs
# github.com/rs/xid v1.2.1
## explicit
github.com/rs/xid
# github.com/russross/blackfriday/v2 v2.0.1
# github.com/russross/blackfriday/v2 v2.1.0
## explicit
github.com/russross/blackfriday/v2
# github.com/shurcooL/sanitized_anchor_name v1.0.0
github.com/shurcooL/sanitized_anchor_name
# github.com/stretchr/objx v0.1.1
github.com/stretchr/objx
# github.com/stretchr/testify v1.6.1
@@ -197,7 +195,7 @@ github.com/umputun/go-flags
github.com/xdg/scram
# github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc
github.com/xdg/stringprep
# go.etcd.io/bbolt v1.3.4
# go.etcd.io/bbolt v1.3.5
## explicit
go.etcd.io/bbolt
# go.mongodb.org/mongo-driver v1.4.4