├── README.md ├── bit.go ├── bit_test.go ├── duration.go ├── duration_test.go ├── go.mod ├── iso8601.go ├── net.go ├── random.go ├── ratelimit ├── go.mod ├── go.sum ├── iowrapper.go └── iowrapper_test.go ├── sampler.go ├── sampler_test.go ├── structcopy.go └── structcopy_test.go /README.md: -------------------------------------------------------------------------------- 1 | # gotools 2 | tools for golang 3 | 4 | ## StructCopy 5 | Copy fields which have same name and types between different types of struct. Note that unexported fields are ignored. 6 | 7 | ```go 8 | type Src struct { 9 | A int 10 | B string 11 | c byte 12 | } 13 | 14 | type Dst struct { 15 | A int 16 | B byte 17 | c byte 18 | } 19 | 20 | func main(){ 21 | src := &Src{3, "hello", '2'} 22 | var dst Dst 23 | gotools.StructCopy(&dst, src) 24 | fmt.Println(dst) 25 | } 26 | 27 | /* output 28 | {3 0 0} 29 | */ 30 | ``` 31 | -------------------------------------------------------------------------------- /bit.go: -------------------------------------------------------------------------------- 1 | package gotools 2 | 3 | func GetBitR2L(d uint64, idx uint) uint64 { 4 | return d >> idx & 1 5 | } 6 | 7 | func GetBit32L2R(d uint32, idx uint) uint32 { 8 | return d >> (32 - idx - 1) & 1 9 | } 10 | -------------------------------------------------------------------------------- /bit_test.go: -------------------------------------------------------------------------------- 1 | package gotools 2 | 3 | import "testing" 4 | 5 | func TestGetBitR2L(t *testing.T) { 6 | idx := []uint{2, 3, 5, 8, 9, 11} 7 | value := []uint64{0, 1, 0, 1, 1, 0} 8 | for i := range idx { 9 | actual := GetBitR2L(5896, idx[i]) 10 | if actual != value[i] { 11 | t.Fatalf("GetBit32(5896, %d) wrong, expected %d, actual %d", idx[i], value[i], actual) 12 | } 13 | } 14 | } 15 | 16 | func TestGetBit32L2R(t *testing.T) { 17 | idx := []uint{2, 3, 5, 8, 9, 10} 18 | value := []uint32{1, 0, 0, 0, 0, 1} 19 | for i := range idx { 20 | actual := GetBit32L2R(589685412, idx[i]) 21 | if actual != value[i] { 22 | t.Fatalf("GetBit32(5896, %d) wrong, expected %d, actual %d", idx[i], value[i], actual) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /duration.go: -------------------------------------------------------------------------------- 1 | package gotools 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "time" 7 | ) 8 | 9 | // Duration extend time.Duration to support json.Unmarshal from string. 10 | // copy from https://stackoverflow.com/questions/48050945/how-to-unmarshal-json-into-durations 11 | type Duration struct { 12 | time.Duration 13 | } 14 | 15 | // MarshalJSON implement json marshal 16 | func (d Duration) MarshalJSON() ([]byte, error) { 17 | return json.Marshal(d.String()) 18 | } 19 | 20 | // UnmarshalJSON implement json unmarshal 21 | func (d *Duration) UnmarshalJSON(b []byte) error { 22 | var v interface{} 23 | if err := json.Unmarshal(b, &v); err != nil { 24 | return err 25 | } 26 | switch value := v.(type) { 27 | case float64: 28 | d.Duration = time.Duration(value) 29 | return nil 30 | case string: 31 | var err error 32 | d.Duration, err = time.ParseDuration(value) 33 | if err != nil { 34 | return err 35 | } 36 | return nil 37 | default: 38 | return errors.New("invalid duration") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /duration_test.go: -------------------------------------------------------------------------------- 1 | package gotools 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | type TestDuration struct { 10 | Timeout Duration 11 | } 12 | 13 | func TestDurationUnmarshal(t *testing.T) { 14 | rawbytes := []byte(`{"Timeout": "10s"}`) 15 | var td TestDuration 16 | if err := json.Unmarshal(rawbytes, &td); err != nil { 17 | t.Fatalf("unmarshal failed: %v", err) 18 | } 19 | if td.Timeout.Duration != 10*time.Second { 20 | t.Fatalf("unmarshal failed. value of Timeout wrong: %s", td.Timeout) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gokits/gotools 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /iso8601.go: -------------------------------------------------------------------------------- 1 | package gotools 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func ISO8601(t *time.Time) string { 9 | var tz string 10 | z, off := t.Zone() 11 | if z == "UTC" { 12 | tz = "Z" 13 | } else { 14 | tz = fmt.Sprintf("%03d00", off/3600) 15 | } 16 | return fmt.Sprintf("%04d-%02d-%02dT%02d:%02d:%02d%s", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 17 | t.Second(), tz) 18 | } 19 | -------------------------------------------------------------------------------- /net.go: -------------------------------------------------------------------------------- 1 | package gotools 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | type IPVersion int 10 | 11 | const ( 12 | VersionUnknown IPVersion = iota 13 | IPv4 IPVersion = iota 14 | IPv6 IPVersion = iota 15 | ) 16 | 17 | func ParseIPVersion(str string) IPVersion { 18 | ip := net.ParseIP(str) 19 | if ip == nil { 20 | return VersionUnknown 21 | } 22 | if strings.Contains(str, ":") { 23 | return IPv6 24 | } 25 | return IPv4 26 | } 27 | 28 | func IPv4ByIfaceName(iface string) (ips []net.IP, err error) { 29 | var ( 30 | i *net.Interface 31 | addrs []net.Addr 32 | ip net.IP 33 | ) 34 | i, err = net.InterfaceByName(iface) 35 | if err != nil { 36 | return 37 | } 38 | addrs, err = i.Addrs() 39 | if err != nil { 40 | return 41 | } 42 | for _, addr := range addrs { 43 | ip, _, err = net.ParseCIDR(addr.String()) 44 | if err != nil { 45 | continue 46 | } 47 | if ParseIPVersion(ip.String()) == IPv4 { 48 | ips = append(ips, ip) 49 | } 50 | } 51 | return 52 | } 53 | 54 | //convert ip from int64 to string 55 | func InetNtoa(ipnr int64) string { 56 | var bytes [4]byte 57 | bytes[0] = byte(ipnr & 0xFF) 58 | bytes[1] = byte((ipnr >> 8) & 0xFF) 59 | bytes[2] = byte((ipnr >> 16) & 0xFF) 60 | bytes[3] = byte((ipnr >> 24) & 0xFF) 61 | ip := net.IPv4(bytes[3], bytes[2], bytes[1], bytes[0]) 62 | return ip.String() 63 | } 64 | 65 | //convert ip from string to int64 66 | func InetAton(ipnr string) int64 { 67 | bits := strings.Split(ipnr, ".") 68 | if len(bits) < 4 { 69 | return 0 70 | } 71 | b0, _ := strconv.Atoi(bits[0]) 72 | b1, _ := strconv.Atoi(bits[1]) 73 | b2, _ := strconv.Atoi(bits[2]) 74 | b3, _ := strconv.Atoi(bits[3]) 75 | var sum int64 76 | sum += int64(b0) << 24 77 | sum += int64(b1) << 16 78 | sum += int64(b2) << 8 79 | sum += int64(b3) 80 | return sum 81 | } 82 | 83 | func GetHostIPs(filter func(ip string) bool) (ips []string) { 84 | inters, _ := net.Interfaces() 85 | for _, inter := range inters { 86 | addrs, _ := inter.Addrs() 87 | if addrs != nil { 88 | ipaddr := strings.Split(addrs[0].String(), `/`) 89 | if filter(ipaddr[0]) { 90 | ips = append(ips, ipaddr[0]) 91 | } 92 | } 93 | } 94 | return 95 | } 96 | -------------------------------------------------------------------------------- /random.go: -------------------------------------------------------------------------------- 1 | package gotools 2 | 3 | import ( 4 | "bytes" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func init() { 10 | rand.Seed(time.Now().UTC().UnixNano()) 11 | } 12 | 13 | func RandomString(num int) string { 14 | var result bytes.Buffer 15 | for i := 0; i < num; i++ { 16 | result.WriteRune(rune(RandomInt(65, 90))) 17 | } 18 | return result.String() 19 | } 20 | 21 | func RandomIntString(length int) string { 22 | var result bytes.Buffer 23 | for i := 0; i < length; i++ { 24 | result.WriteRune(rune(RandomInt(48, 57))) 25 | } 26 | return result.String() 27 | } 28 | 29 | func RandomInt(min int, max int) int { 30 | return min + rand.Intn(max-min) 31 | } 32 | -------------------------------------------------------------------------------- /ratelimit/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gokits/gotools/ratelimit 2 | 3 | go 1.15 4 | 5 | require golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e 6 | -------------------------------------------------------------------------------- /ratelimit/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= 2 | golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 3 | -------------------------------------------------------------------------------- /ratelimit/iowrapper.go: -------------------------------------------------------------------------------- 1 | package ratelimit 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | "time" 8 | 9 | "golang.org/x/time/rate" 10 | ) 11 | 12 | type tbReader struct { 13 | r io.Reader 14 | l *rate.Limiter 15 | } 16 | 17 | func (r *tbReader) Read(buf []byte) (int, error) { 18 | if r.l == nil { 19 | return r.r.Read(buf) 20 | } 21 | ctx := context.Background() 22 | max := len(buf) 23 | total := 0 24 | burst := r.l.Burst() 25 | for total < max { 26 | next := total + burst 27 | if next > max { 28 | next = max 29 | } 30 | n, e := r.r.Read(buf[total:next]) 31 | if n <= 0 { 32 | return total, e 33 | } 34 | total += n 35 | ee := r.l.WaitN(ctx, n) 36 | if ee != nil { 37 | return total, ee 38 | } 39 | } 40 | return total, nil 41 | } 42 | 43 | func TokenBucketReader(r io.Reader, l *rate.Limiter) io.Reader { 44 | return &tbReader{ 45 | r: r, 46 | l: l, 47 | } 48 | } 49 | 50 | type tbWriter struct { 51 | w io.Writer 52 | l *rate.Limiter 53 | } 54 | 55 | func (w *tbWriter) Write(buf []byte) (int, error) { 56 | if w.l == nil { 57 | return w.w.Write(buf) 58 | } 59 | ctx := context.Background() 60 | max := len(buf) 61 | burst := w.l.Burst() 62 | total := 0 63 | for total < max { 64 | next := total + burst 65 | if next > max { 66 | next = max 67 | } 68 | if e := w.l.WaitN(ctx, next-total); e != nil { 69 | return total, e 70 | } 71 | n, e := w.w.Write(buf[total:next]) 72 | if e != nil { 73 | return total + n, e 74 | } 75 | total += n 76 | } 77 | return total, nil 78 | } 79 | 80 | func TokenBucketWriter(w io.Writer, l *rate.Limiter) io.Writer { 81 | return &tbWriter{ 82 | w: w, 83 | l: l, 84 | } 85 | } 86 | 87 | type tbConn struct { 88 | conn net.Conn 89 | tbReader 90 | tbWriter 91 | } 92 | 93 | func (tbc *tbConn) Close() error { 94 | return tbc.conn.Close() 95 | } 96 | 97 | func (tbc *tbConn) LocalAddr() net.Addr { 98 | return tbc.conn.LocalAddr() 99 | } 100 | 101 | func (tbc *tbConn) RemoteAddr() net.Addr { 102 | return tbc.conn.RemoteAddr() 103 | } 104 | 105 | func (tbc *tbConn) SetDeadline(t time.Time) error { 106 | return tbc.conn.SetDeadline(t) 107 | } 108 | 109 | func (tbc *tbConn) SetWriteDeadline(t time.Time) error { 110 | return tbc.conn.SetWriteDeadline(t) 111 | } 112 | 113 | func (tbc *tbConn) SetReadDeadline(t time.Time) error { 114 | return tbc.conn.SetReadDeadline(t) 115 | } 116 | 117 | func TokenBucketConn(c net.Conn, readlimiter, writelimiter *rate.Limiter) net.Conn { 118 | return &tbConn{ 119 | conn: c, 120 | tbReader: tbReader{ 121 | r: c, 122 | l: readlimiter, 123 | }, 124 | tbWriter: tbWriter{ 125 | w: c, 126 | l: writelimiter, 127 | }, 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /ratelimit/iowrapper_test.go: -------------------------------------------------------------------------------- 1 | package ratelimit 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "math/rand" 7 | "testing" 8 | "time" 9 | 10 | "golang.org/x/time/rate" 11 | ) 12 | 13 | func TestReader(t *testing.T) { 14 | b := make([]byte, 10*1024*1024) 15 | _, _ = rand.Read(b) 16 | r := TokenBucketReader(bytes.NewReader(b), rate.NewLimiter(1024*1024, 64*1024)) 17 | p := make([]byte, len(b)) 18 | w := bytes.NewBuffer(p) 19 | now := time.Now() 20 | tmpbuf := make([]byte, 4*1024) 21 | n, err := io.CopyBuffer(w, r, tmpbuf) 22 | elapsed := time.Since(now) 23 | if err != nil && err != io.EOF { 24 | t.Fatalf("copy error: %v", err) 25 | } 26 | if n != 10*1024*1024 { 27 | t.Fatalf("n should be %d, actual %d", 10*1024*1024, n) 28 | } 29 | if elapsed < 9*time.Second || elapsed > 11*time.Second { 30 | t.Fatalf("elapsed should be around 10s, actual %dms", elapsed/time.Millisecond) 31 | } 32 | } 33 | 34 | func TestWriter(t *testing.T) { 35 | out := make([]byte, 10*1024*1024) 36 | _, _ = rand.Read(out) 37 | in := make([]byte, len(out)) 38 | r := bytes.NewBuffer(out) 39 | w := TokenBucketWriter(bytes.NewBuffer(in), rate.NewLimiter(1024*1024, 64*1024)) 40 | now := time.Now() 41 | tmpbuf := make([]byte, 4*1024) 42 | n, err := io.CopyBuffer(w, r, tmpbuf) 43 | elapsed := time.Since(now) 44 | if err != nil && err != io.EOF { 45 | t.Fatalf("copy error: %v", err) 46 | } 47 | if n != 10*1024*1024 { 48 | t.Fatalf("n should be %d, actual %d", 10*1024*1024, n) 49 | } 50 | if elapsed < 9*time.Second || elapsed > 11*time.Second { 51 | t.Fatalf("elapsed should be around 10s, actual %dms", elapsed/time.Millisecond) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /sampler.go: -------------------------------------------------------------------------------- 1 | package gotools 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | type RandSampler struct { 10 | r *rand.Rand 11 | base int32 12 | ratio int32 13 | } 14 | 15 | func NewRandSampler(ratio float64) (*RandSampler, error) { 16 | if ratio < 0 || ratio > 1 { 17 | return nil, fmt.Errorf("ratio %f invalid, must between 0 and 1", ratio) 18 | } 19 | s := rand.NewSource(time.Now().UnixNano()) 20 | return &RandSampler{ 21 | r: rand.New(s), 22 | base: 10000, 23 | ratio: int32(ratio * 10000), 24 | }, nil 25 | } 26 | 27 | func (rs *RandSampler) Sample() bool { 28 | return rs.r.Int63n(int64(rs.base)) < int64(rs.ratio) 29 | } 30 | -------------------------------------------------------------------------------- /sampler_test.go: -------------------------------------------------------------------------------- 1 | package gotools 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewSampler(t *testing.T) { 8 | var err error 9 | type testNewParam struct { 10 | ratio float64 11 | success bool 12 | } 13 | params := []testNewParam{ 14 | testNewParam{-1, false}, 15 | testNewParam{0, true}, 16 | testNewParam{0.3, true}, 17 | testNewParam{1, true}, 18 | testNewParam{2, false}, 19 | testNewParam{0.5, true}, 20 | } 21 | for _, p := range params { 22 | if _, err = NewRandSampler(p.ratio); (err != nil && p.success) || (err == nil && !p.success) { 23 | t.Errorf("param(ratio= %f), success should be %v, but err = %v", p.ratio, p.success, err) 24 | } 25 | } 26 | } 27 | 28 | func TestRandSamplerCorrectness(t *testing.T) { 29 | type Param struct { 30 | ratio float64 31 | loopcnt int64 32 | shotcnt int64 33 | } 34 | params := []Param{ 35 | Param{0, 1000000, 0}, 36 | Param{1, 1000000, 1000000}, 37 | Param{0.2, 1000000, 200000}, 38 | Param{0.3, 1000000, 300000}, 39 | Param{0.7, 1000000, 700000}, 40 | Param{0.3, 1000000, 300000}, 41 | } 42 | var i int64 43 | 44 | for _, p := range params { 45 | s, _ := NewRandSampler(p.ratio) 46 | shotcnt := 0 47 | for i = 0; i < p.loopcnt; i++ { 48 | if s.Sample() { 49 | shotcnt++ 50 | } 51 | } 52 | if float64(shotcnt) > float64(p.shotcnt)*1.01 || float64(shotcnt) < float64(p.shotcnt)*0.99 { 53 | t.Errorf("param = %+v failed, actual shotcnt = %d, sampler = %+v", p, shotcnt, s) 54 | } else { 55 | t.Logf("param = %+v success, actual shotcnt = %d", p, shotcnt) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /structcopy.go: -------------------------------------------------------------------------------- 1 | package gotools 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | func DeepFields(ifaceType reflect.Type) []reflect.StructField { 8 | var fields []reflect.StructField 9 | 10 | for i := 0; i < ifaceType.NumField(); i++ { 11 | v := ifaceType.Field(i) 12 | if v.Anonymous && v.Type.Kind() == reflect.Struct { 13 | fields = append(fields, DeepFields(v.Type)...) 14 | } else { 15 | fields = append(fields, v) 16 | } 17 | } 18 | 19 | return fields 20 | } 21 | 22 | func StructCopy(DstStructPtr interface{}, SrcStructPtr interface{}) { 23 | srcv := reflect.ValueOf(SrcStructPtr) 24 | dstv := reflect.ValueOf(DstStructPtr) 25 | srct := reflect.TypeOf(SrcStructPtr) 26 | dstt := reflect.TypeOf(DstStructPtr) 27 | if srct.Kind() != reflect.Ptr || dstt.Kind() != reflect.Ptr || 28 | srct.Elem().Kind() == reflect.Ptr || dstt.Elem().Kind() == reflect.Ptr { 29 | panic("Fatal error:type of parameters must be Ptr of value") 30 | } 31 | if srcv.IsNil() || dstv.IsNil() { 32 | panic("Fatal error:value of parameters should not be nil") 33 | } 34 | srcV := srcv.Elem() 35 | dstV := dstv.Elem() 36 | srcfields := DeepFields(reflect.ValueOf(SrcStructPtr).Elem().Type()) 37 | for _, v := range srcfields { 38 | if v.Anonymous { 39 | continue 40 | } 41 | dst := dstV.FieldByName(v.Name) 42 | src := srcV.FieldByName(v.Name) 43 | if !dst.IsValid() { 44 | continue 45 | } 46 | if src.Type() == dst.Type() && dst.CanSet() { 47 | dst.Set(src) 48 | continue 49 | } 50 | if src.Kind() == reflect.Ptr && !src.IsNil() && src.Type().Elem() == dst.Type() { 51 | dst.Set(src.Elem()) 52 | continue 53 | } 54 | if dst.Kind() == reflect.Ptr && dst.Type().Elem() == src.Type() { 55 | dst.Set(reflect.New(src.Type())) 56 | dst.Elem().Set(src) 57 | continue 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /structcopy_test.go: -------------------------------------------------------------------------------- 1 | package gotools 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type AA struct { 9 | A string 10 | C int 11 | } 12 | 13 | type BB struct { 14 | AA 15 | D bool 16 | E map[string]string 17 | } 18 | 19 | type CC struct { 20 | A string 21 | C string 22 | D bool 23 | E string 24 | } 25 | 26 | func TestEmbedded(t *testing.T) { 27 | var cc CC 28 | bb := BB{ 29 | AA: AA{ 30 | A: "dsdd", 31 | C: 3, 32 | }, 33 | D: true, 34 | E: map[string]string{"ab": "cc", "a": "ee"}, 35 | } 36 | StructCopy(&cc, &bb) 37 | if cc.A != "dsdd" { 38 | t.Error("field A failed") 39 | } 40 | if cc.D != true { 41 | t.Error("field D failed") 42 | } 43 | if cc.C != "" { 44 | t.Error("field C failed") 45 | } 46 | } 47 | 48 | type WithPtr struct { 49 | A *string 50 | B int 51 | C *bool 52 | D *[]int 53 | E *string 54 | G string 55 | } 56 | 57 | type NoPtr struct { 58 | B int 59 | C bool 60 | D []int 61 | E string 62 | F bool 63 | G *string 64 | } 65 | 66 | func TestSrcPtr(t *testing.T) { 67 | var a string = "aaa" 68 | var e string = "eeee" 69 | var c bool = true 70 | var d []int = []int{3, 2} 71 | src := WithPtr{ 72 | A: &a, 73 | B: 3, 74 | C: &c, 75 | D: &d, 76 | E: &e, 77 | G: "dddd", 78 | } 79 | 80 | var dst NoPtr 81 | StructCopy(&dst, &src) 82 | if src.B != dst.B { 83 | t.Error("field B failed") 84 | } 85 | if *src.C != dst.C { 86 | t.Error("field C failed") 87 | } 88 | if !reflect.DeepEqual(*src.D, dst.D) { 89 | t.Error("field D failed") 90 | } 91 | if *src.E != dst.E { 92 | t.Error("field E failed") 93 | } 94 | if dst.G == nil || *dst.G != src.G { 95 | t.Error("field G failed") 96 | } 97 | } 98 | 99 | func TestDstPtr(t *testing.T) { 100 | var e string = "eeee" 101 | var c bool = true 102 | var d []int = []int{3, 2} 103 | src := NoPtr{ 104 | B: 3, 105 | C: c, 106 | D: d, 107 | E: e, 108 | } 109 | 110 | var dst WithPtr 111 | StructCopy(&dst, &src) 112 | if dst.A != nil { 113 | t.Error("field A failed") 114 | } 115 | if src.B != dst.B { 116 | t.Error("field B failed") 117 | } 118 | if *dst.C != src.C { 119 | t.Error("field C failed") 120 | } 121 | if !reflect.DeepEqual(*dst.D, src.D) { 122 | t.Error("field D failed") 123 | } 124 | if *dst.E != src.E { 125 | t.Error("field E failed") 126 | } 127 | if dst.G != "" { 128 | t.Error("field G failed") 129 | } 130 | } 131 | --------------------------------------------------------------------------------