From 51501f920c092791c7d83ac7067874547a37c96a Mon Sep 17 00:00:00 2001 From: David Lu Date: Tue, 6 Sep 2016 18:51:27 -0400 Subject: PLT-3753 Added Segment analytics (#3972) --- vendor/github.com/jehiah/go-strftime/README.md | 4 + vendor/github.com/jehiah/go-strftime/strftime.go | 71 +++ .../github.com/jehiah/go-strftime/strftime_test.go | 40 ++ .../segmentio/analytics-go/Godeps/Godeps.json | 17 + .../segmentio/analytics-go/Godeps/Readme | 5 + .../analytics-go/Godeps/_workspace/.gitignore | 2 + .../src/github.com/jehiah/go-strftime/.gitignore | 22 + .../src/github.com/jehiah/go-strftime/README.md | 4 + .../src/github.com/jehiah/go-strftime/strftime.go | 71 +++ .../github.com/jehiah/go-strftime/strftime_test.go | 40 ++ .../_workspace/src/github.com/xtgo/uuid/AUTHORS | 5 + .../_workspace/src/github.com/xtgo/uuid/LICENSE | 27 ++ .../_workspace/src/github.com/xtgo/uuid/uuid.go | 204 +++++++++ .../src/github.com/xtgo/uuid/uuid_test.go | 102 +++++ .../github.com/segmentio/analytics-go/History.md | 67 +++ vendor/github.com/segmentio/analytics-go/Makefile | 10 + vendor/github.com/segmentio/analytics-go/Readme.md | 8 + .../github.com/segmentio/analytics-go/analytics.go | 407 ++++++++++++++++++ .../segmentio/analytics-go/analytics_test.go | 478 +++++++++++++++++++++ .../github.com/segmentio/analytics-go/circle.yml | 18 + .../github.com/segmentio/analytics-go/cli/cli.go | 174 ++++++++ .../segmentio/analytics-go/examples/track.go | 36 ++ vendor/github.com/segmentio/backo-go/README.md | 80 ++++ vendor/github.com/segmentio/backo-go/backo.go | 83 ++++ vendor/github.com/segmentio/backo-go/backo_test.go | 77 ++++ vendor/github.com/xtgo/uuid/AUTHORS | 5 + vendor/github.com/xtgo/uuid/LICENSE | 27 ++ vendor/github.com/xtgo/uuid/uuid.go | 204 +++++++++ vendor/github.com/xtgo/uuid/uuid_test.go | 102 +++++ 29 files changed, 2390 insertions(+) create mode 100644 vendor/github.com/jehiah/go-strftime/README.md create mode 100644 vendor/github.com/jehiah/go-strftime/strftime.go create mode 100644 vendor/github.com/jehiah/go-strftime/strftime_test.go create mode 100644 vendor/github.com/segmentio/analytics-go/Godeps/Godeps.json create mode 100644 vendor/github.com/segmentio/analytics-go/Godeps/Readme create mode 100644 vendor/github.com/segmentio/analytics-go/Godeps/_workspace/.gitignore create mode 100644 vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/.gitignore create mode 100644 vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/README.md create mode 100644 vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime.go create mode 100644 vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime_test.go create mode 100644 vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/AUTHORS create mode 100644 vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/LICENSE create mode 100644 vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid.go create mode 100644 vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid_test.go create mode 100644 vendor/github.com/segmentio/analytics-go/History.md create mode 100644 vendor/github.com/segmentio/analytics-go/Makefile create mode 100644 vendor/github.com/segmentio/analytics-go/Readme.md create mode 100644 vendor/github.com/segmentio/analytics-go/analytics.go create mode 100644 vendor/github.com/segmentio/analytics-go/analytics_test.go create mode 100644 vendor/github.com/segmentio/analytics-go/circle.yml create mode 100644 vendor/github.com/segmentio/analytics-go/cli/cli.go create mode 100644 vendor/github.com/segmentio/analytics-go/examples/track.go create mode 100644 vendor/github.com/segmentio/backo-go/README.md create mode 100644 vendor/github.com/segmentio/backo-go/backo.go create mode 100644 vendor/github.com/segmentio/backo-go/backo_test.go create mode 100644 vendor/github.com/xtgo/uuid/AUTHORS create mode 100644 vendor/github.com/xtgo/uuid/LICENSE create mode 100644 vendor/github.com/xtgo/uuid/uuid.go create mode 100644 vendor/github.com/xtgo/uuid/uuid_test.go (limited to 'vendor') diff --git a/vendor/github.com/jehiah/go-strftime/README.md b/vendor/github.com/jehiah/go-strftime/README.md new file mode 100644 index 000000000..8eb240384 --- /dev/null +++ b/vendor/github.com/jehiah/go-strftime/README.md @@ -0,0 +1,4 @@ +go-strftime +=========== + +go implementation of strftime \ No newline at end of file diff --git a/vendor/github.com/jehiah/go-strftime/strftime.go b/vendor/github.com/jehiah/go-strftime/strftime.go new file mode 100644 index 000000000..99e26716f --- /dev/null +++ b/vendor/github.com/jehiah/go-strftime/strftime.go @@ -0,0 +1,71 @@ +// go implementation of strftime +package strftime + +import ( + "strings" + "time" +) + +// taken from time/format.go +var conversion = map[rune]string { + /*stdLongMonth */ 'B':"January", + /*stdMonth */ 'b': "Jan", + // stdNumMonth */ 'm': "1", + /*stdZeroMonth */ 'm': "01", + /*stdLongWeekDay */ 'A': "Monday", + /*stdWeekDay */ 'a': "Mon", + // stdDay */ 'd': "2", + // stdUnderDay */ 'd': "_2", + /*stdZeroDay */ 'd': "02", + /*stdHour */ 'H': "15", + // stdHour12 */ 'I': "3", + /*stdZeroHour12 */ 'I': "03", + // stdMinute */ 'M': "4", + /*stdZeroMinute */ 'M': "04", + // stdSecond */ 'S': "5", + /*stdZeroSecond */ 'S': "05", + /*stdLongYear */ 'Y': "2006", + /*stdYear */ 'y': "06", + /*stdPM */ 'p': "PM", + // stdpm */ 'p': "pm", + /*stdTZ */ 'Z': "MST", + // stdISO8601TZ */ 'z': "Z0700", // prints Z for UTC + // stdISO8601ColonTZ */ 'z': "Z07:00", // prints Z for UTC + /*stdNumTZ */ 'z': "-0700", // always numeric + // stdNumShortTZ */ 'b': "-07", // always numeric + // stdNumColonTZ */ 'b': "-07:00", // always numeric +} + +// This is an alternative to time.Format because no one knows +// what date 040305 is supposed to create when used as a 'layout' string +// this takes standard strftime format options. For a complete list +// of format options see http://strftime.org/ +func Format(format string, t time.Time) string { + retval := make([]byte, 0, len(format)) + for i, ni := 0, 0; i < len(format); i = ni + 2 { + ni = strings.IndexByte(format[i:], '%') + if ni < 0 { + ni = len(format) + } else { + ni += i + } + retval = append(retval, []byte(format[i:ni])...) + if ni + 1 < len(format) { + c := format[ni + 1] + if c == '%' { + retval = append(retval, '%') + } else { + if layoutCmd, ok := conversion[rune(c)]; ok { + retval = append(retval, []byte(t.Format(layoutCmd))...) + } else { + retval = append(retval, '%', c) + } + } + } else { + if ni < len(format) { + retval = append(retval, '%') + } + } + } + return string(retval) +} diff --git a/vendor/github.com/jehiah/go-strftime/strftime_test.go b/vendor/github.com/jehiah/go-strftime/strftime_test.go new file mode 100644 index 000000000..45cbca345 --- /dev/null +++ b/vendor/github.com/jehiah/go-strftime/strftime_test.go @@ -0,0 +1,40 @@ +package strftime + +import ( + "time" + "fmt" + "testing" +) + +func ExampleFormat() { + t := time.Unix(1340244776, 0) + utc, _ := time.LoadLocation("UTC") + t = t.In(utc) + fmt.Println(Format("%Y-%m-%d %H:%M:%S", t)) + // Output: + // 2012-06-21 02:12:56 +} + +func TestNoLeadingPercentSign(t *testing.T) { + tm := time.Unix(1340244776, 0) + utc, _ := time.LoadLocation("UTC") + tm = tm.In(utc) + result := Format("aaabbb0123456789%Y", tm) + if result != "aaabbb01234567892012" { + t.Logf("%s != %s", result, "aaabbb01234567892012") + t.Fail() + } +} + + +func TestUnsupported(t *testing.T) { + tm := time.Unix(1340244776, 0) + utc, _ := time.LoadLocation("UTC") + tm = tm.In(utc) + result := Format("%0%1%%%2", tm) + if result != "%0%1%%2" { + t.Logf("%s != %s", result, "%0%1%%2") + t.Fail() + } +} + diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/Godeps.json b/vendor/github.com/segmentio/analytics-go/Godeps/Godeps.json new file mode 100644 index 000000000..e1b79eb2d --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/Godeps/Godeps.json @@ -0,0 +1,17 @@ +{ + "ImportPath": "github.com/segmentio/analytics-go", + "GoVersion": "go1.4.2", + "Packages": [ + "./..." + ], + "Deps": [ + { + "ImportPath": "github.com/jehiah/go-strftime", + "Rev": "834e15c05a45371503440cc195bbd05c9a0968d9" + }, + { + "ImportPath": "github.com/xtgo/uuid", + "Rev": "a0b114877d4caeffbd7f87e3757c17fce570fea7" + } + ] +} diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/Readme b/vendor/github.com/segmentio/analytics-go/Godeps/Readme new file mode 100644 index 000000000..4cdaa53d5 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/Godeps/Readme @@ -0,0 +1,5 @@ +This directory tree is generated automatically by godep. + +Please do not edit. + +See https://github.com/tools/godep for more information. diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/.gitignore b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/.gitignore new file mode 100644 index 000000000..f037d684e --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/.gitignore @@ -0,0 +1,2 @@ +/pkg +/bin diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/.gitignore b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/.gitignore new file mode 100644 index 000000000..00268614f --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/README.md b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/README.md new file mode 100644 index 000000000..8eb240384 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/README.md @@ -0,0 +1,4 @@ +go-strftime +=========== + +go implementation of strftime \ No newline at end of file diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime.go b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime.go new file mode 100644 index 000000000..99e26716f --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime.go @@ -0,0 +1,71 @@ +// go implementation of strftime +package strftime + +import ( + "strings" + "time" +) + +// taken from time/format.go +var conversion = map[rune]string { + /*stdLongMonth */ 'B':"January", + /*stdMonth */ 'b': "Jan", + // stdNumMonth */ 'm': "1", + /*stdZeroMonth */ 'm': "01", + /*stdLongWeekDay */ 'A': "Monday", + /*stdWeekDay */ 'a': "Mon", + // stdDay */ 'd': "2", + // stdUnderDay */ 'd': "_2", + /*stdZeroDay */ 'd': "02", + /*stdHour */ 'H': "15", + // stdHour12 */ 'I': "3", + /*stdZeroHour12 */ 'I': "03", + // stdMinute */ 'M': "4", + /*stdZeroMinute */ 'M': "04", + // stdSecond */ 'S': "5", + /*stdZeroSecond */ 'S': "05", + /*stdLongYear */ 'Y': "2006", + /*stdYear */ 'y': "06", + /*stdPM */ 'p': "PM", + // stdpm */ 'p': "pm", + /*stdTZ */ 'Z': "MST", + // stdISO8601TZ */ 'z': "Z0700", // prints Z for UTC + // stdISO8601ColonTZ */ 'z': "Z07:00", // prints Z for UTC + /*stdNumTZ */ 'z': "-0700", // always numeric + // stdNumShortTZ */ 'b': "-07", // always numeric + // stdNumColonTZ */ 'b': "-07:00", // always numeric +} + +// This is an alternative to time.Format because no one knows +// what date 040305 is supposed to create when used as a 'layout' string +// this takes standard strftime format options. For a complete list +// of format options see http://strftime.org/ +func Format(format string, t time.Time) string { + retval := make([]byte, 0, len(format)) + for i, ni := 0, 0; i < len(format); i = ni + 2 { + ni = strings.IndexByte(format[i:], '%') + if ni < 0 { + ni = len(format) + } else { + ni += i + } + retval = append(retval, []byte(format[i:ni])...) + if ni + 1 < len(format) { + c := format[ni + 1] + if c == '%' { + retval = append(retval, '%') + } else { + if layoutCmd, ok := conversion[rune(c)]; ok { + retval = append(retval, []byte(t.Format(layoutCmd))...) + } else { + retval = append(retval, '%', c) + } + } + } else { + if ni < len(format) { + retval = append(retval, '%') + } + } + } + return string(retval) +} diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime_test.go b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime_test.go new file mode 100644 index 000000000..45cbca345 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/jehiah/go-strftime/strftime_test.go @@ -0,0 +1,40 @@ +package strftime + +import ( + "time" + "fmt" + "testing" +) + +func ExampleFormat() { + t := time.Unix(1340244776, 0) + utc, _ := time.LoadLocation("UTC") + t = t.In(utc) + fmt.Println(Format("%Y-%m-%d %H:%M:%S", t)) + // Output: + // 2012-06-21 02:12:56 +} + +func TestNoLeadingPercentSign(t *testing.T) { + tm := time.Unix(1340244776, 0) + utc, _ := time.LoadLocation("UTC") + tm = tm.In(utc) + result := Format("aaabbb0123456789%Y", tm) + if result != "aaabbb01234567892012" { + t.Logf("%s != %s", result, "aaabbb01234567892012") + t.Fail() + } +} + + +func TestUnsupported(t *testing.T) { + tm := time.Unix(1340244776, 0) + utc, _ := time.LoadLocation("UTC") + tm = tm.In(utc) + result := Format("%0%1%%%2", tm) + if result != "%0%1%%2" { + t.Logf("%s != %s", result, "%0%1%%2") + t.Fail() + } +} + diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/AUTHORS b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/AUTHORS new file mode 100644 index 000000000..a6f045160 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/AUTHORS @@ -0,0 +1,5 @@ +# This source file refers to The gocql Authors for copyright purposes. + +Christoph Hack +Jonathan Rudenberg +Thorsten von Eicken diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/LICENSE b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/LICENSE new file mode 100644 index 000000000..18d25f923 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The gocql Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid.go b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid.go new file mode 100644 index 000000000..a0fd7a5a5 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid.go @@ -0,0 +1,204 @@ +// Copyright (c) 2012 The gocql Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package uuid can be used to generate and parse universally unique +// identifiers, a standardized format in the form of a 128 bit number. +// +// http://tools.ietf.org/html/rfc4122 +package uuid + +import ( + "crypto/rand" + "encoding/hex" + "errors" + "io" + "net" + "strconv" + "time" +) + +type UUID [16]byte + +var hardwareAddr []byte + +const ( + VariantNCSCompat = 0 + VariantIETF = 2 + VariantMicrosoft = 6 + VariantFuture = 7 +) + +func init() { + if interfaces, err := net.Interfaces(); err == nil { + for _, i := range interfaces { + if i.Flags&net.FlagLoopback == 0 && len(i.HardwareAddr) > 0 { + hardwareAddr = i.HardwareAddr + break + } + } + } + if hardwareAddr == nil { + // If we failed to obtain the MAC address of the current computer, + // we will use a randomly generated 6 byte sequence instead and set + // the multicast bit as recommended in RFC 4122. + hardwareAddr = make([]byte, 6) + _, err := io.ReadFull(rand.Reader, hardwareAddr) + if err != nil { + panic(err) + } + hardwareAddr[0] = hardwareAddr[0] | 0x01 + } +} + +// Parse parses a 32 digit hexadecimal number (that might contain hyphens) +// representing an UUID. +func Parse(input string) (UUID, error) { + var u UUID + j := 0 + for i := 0; i < len(input); i++ { + b := input[i] + switch { + default: + fallthrough + case j == 32: + goto err + case b == '-': + continue + case '0' <= b && b <= '9': + b -= '0' + case 'a' <= b && b <= 'f': + b -= 'a' - 10 + case 'A' <= b && b <= 'F': + b -= 'A' - 10 + } + u[j/2] |= b << byte(^j&1<<2) + j++ + } + if j == 32 { + return u, nil + } +err: + return UUID{}, errors.New("invalid UUID " + strconv.Quote(input)) +} + +// FromBytes converts a raw byte slice to an UUID. It will panic if the slice +// isn't exactly 16 bytes long. +func FromBytes(input []byte) UUID { + var u UUID + if len(input) != 16 { + panic("UUIDs must be exactly 16 bytes long") + } + copy(u[:], input) + return u +} + +// NewRandom generates a totally random UUID (version 4) as described in +// RFC 4122. +func NewRandom() UUID { + var u UUID + io.ReadFull(rand.Reader, u[:]) + u[6] &= 0x0F // clear version + u[6] |= 0x40 // set version to 4 (random uuid) + u[8] &= 0x3F // clear variant + u[8] |= 0x80 // set to IETF variant + return u +} + +var timeBase = time.Date(1582, time.October, 15, 0, 0, 0, 0, time.UTC).Unix() + +// NewTime generates a new time based UUID (version 1) as described in RFC +// 4122. This UUID contains the MAC address of the node that generated the +// UUID, a timestamp and a sequence number. +func NewTime() UUID { + var u UUID + + now := time.Now().In(time.UTC) + t := uint64(now.Unix()-timeBase)*10000000 + uint64(now.Nanosecond()/100) + u[0], u[1], u[2], u[3] = byte(t>>24), byte(t>>16), byte(t>>8), byte(t) + u[4], u[5] = byte(t>>40), byte(t>>32) + u[6], u[7] = byte(t>>56)&0x0F, byte(t>>48) + + var clockSeq [2]byte + io.ReadFull(rand.Reader, clockSeq[:]) + u[8] = clockSeq[1] + u[9] = clockSeq[0] + + copy(u[10:], hardwareAddr) + + u[6] |= 0x10 // set version to 1 (time based uuid) + u[8] &= 0x3F // clear variant + u[8] |= 0x80 // set to IETF variant + + return u +} + +// String returns the UUID in it's canonical form, a 32 digit hexadecimal +// number in the form of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. +func (u UUID) String() string { + buf := [36]byte{8: '-', 13: '-', 18: '-', 23: '-'} + hex.Encode(buf[0:], u[0:4]) + hex.Encode(buf[9:], u[4:6]) + hex.Encode(buf[14:], u[6:8]) + hex.Encode(buf[19:], u[8:10]) + hex.Encode(buf[24:], u[10:]) + return string(buf[:]) +} + +// Bytes returns the raw byte slice for this UUID. A UUID is always 128 bits +// (16 bytes) long. +func (u UUID) Bytes() []byte { + return u[:] +} + +// Variant returns the variant of this UUID. This package will only generate +// UUIDs in the IETF variant. +func (u UUID) Variant() int { + x := u[8] + switch byte(0) { + case x & 0x80: + return VariantNCSCompat + case x & 0x40: + return VariantIETF + case x & 0x20: + return VariantMicrosoft + } + return VariantFuture +} + +// Version extracts the version of this UUID variant. The RFC 4122 describes +// five kinds of UUIDs. +func (u UUID) Version() int { + return int(u[6] & 0xF0 >> 4) +} + +// Node extracts the MAC address of the node who generated this UUID. It will +// return nil if the UUID is not a time based UUID (version 1). +func (u UUID) Node() []byte { + if u.Version() != 1 { + return nil + } + return u[10:] +} + +// Timestamp extracts the timestamp information from a time based UUID +// (version 1). +func (u UUID) Timestamp() uint64 { + if u.Version() != 1 { + return 0 + } + return uint64(u[0])<<24 + uint64(u[1])<<16 + uint64(u[2])<<8 + + uint64(u[3]) + uint64(u[4])<<40 + uint64(u[5])<<32 + + uint64(u[7])<<48 + uint64(u[6]&0x0F)<<56 +} + +// Time is like Timestamp, except that it returns a time.Time. +func (u UUID) Time() time.Time { + t := u.Timestamp() + if t == 0 { + return time.Time{} + } + sec := t / 10000000 + nsec := t - sec + return time.Unix(int64(sec)+timeBase, int64(nsec)) +} diff --git a/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid_test.go b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid_test.go new file mode 100644 index 000000000..3cef4a31a --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/Godeps/_workspace/src/github.com/xtgo/uuid/uuid_test.go @@ -0,0 +1,102 @@ +// Copyright (c) 2012 The gocql Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "testing" +) + +func TestNil(t *testing.T) { + var uuid UUID + want, got := "00000000-0000-0000-0000-000000000000", uuid.String() + if want != got { + t.Fatalf("TestNil: expected %q got %q", want, got) + } +} + +var tests = []struct { + input string + variant int + version int +}{ + {"b4f00409-cef8-4822-802c-deb20704c365", VariantIETF, 4}, + {"f81d4fae-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1}, + {"00000000-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1}, + {"3051a8d7-aea7-1801-e0bf-bc539dd60cf3", VariantFuture, 1}, + {"3051a8d7-aea7-2801-e0bf-bc539dd60cf3", VariantFuture, 2}, + {"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 3}, + {"3051a8d7-aea7-4801-e0bf-bc539dd60cf3", VariantFuture, 4}, + {"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 5}, + {"d0e817e1-e4b1-1801-3fe6-b4b60ccecf9d", VariantNCSCompat, 0}, + {"d0e817e1-e4b1-1801-bfe6-b4b60ccecf9d", VariantIETF, 1}, + {"d0e817e1-e4b1-1801-dfe6-b4b60ccecf9d", VariantMicrosoft, 0}, + {"d0e817e1-e4b1-1801-ffe6-b4b60ccecf9d", VariantFuture, 0}, +} + +func TestPredefined(t *testing.T) { + for i := range tests { + uuid, err := Parse(tests[i].input) + if err != nil { + t.Errorf("Parse #%d: %v", i, err) + continue + } + + if str := uuid.String(); str != tests[i].input { + t.Errorf("String #%d: expected %q got %q", i, tests[i].input, str) + continue + } + + if variant := uuid.Variant(); variant != tests[i].variant { + t.Errorf("Variant #%d: expected %d got %d", i, tests[i].variant, variant) + } + + if tests[i].variant == VariantIETF { + if version := uuid.Version(); version != tests[i].version { + t.Errorf("Version #%d: expected %d got %d", i, tests[i].version, version) + } + } + } +} + +func TestNewRandom(t *testing.T) { + for i := 0; i < 20; i++ { + uuid := NewRandom() + + if variant := uuid.Variant(); variant != VariantIETF { + t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant) + } + if version := uuid.Version(); version != 4 { + t.Errorf("wrong version. expected %d got %d", 4, version) + } + } +} + +func TestNewTime(t *testing.T) { + var node []byte + timestamp := uint64(0) + for i := 0; i < 20; i++ { + uuid := NewTime() + + if variant := uuid.Variant(); variant != VariantIETF { + t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant) + } + if version := uuid.Version(); version != 1 { + t.Errorf("wrong version. expected %d got %d", 1, version) + } + + if n := uuid.Node(); !bytes.Equal(n, node) && i > 0 { + t.Errorf("wrong node. expected %x, got %x", node, n) + } else if i == 0 { + node = n + } + + ts := uuid.Timestamp() + if ts < timestamp { + t.Errorf("timestamps must grow") + } + timestamp = ts + } +} diff --git a/vendor/github.com/segmentio/analytics-go/History.md b/vendor/github.com/segmentio/analytics-go/History.md new file mode 100644 index 000000000..e8538a0cc --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/History.md @@ -0,0 +1,67 @@ + +v2.1.1 / 2016-04-26 +=================== + + * Fix blocking the goroutine when Close is the first call. + * Fix blocking the goroutine when the message queue fills up. + +v2.1.0 / 2015-12-28 +=================== + + * Add ability to set custom timestamps for messages. + * Add ability to set a custom `net/http` client. + * Add ability to set a custom logger. + * Fix edge case when client would try to upload no messages. + * Properly upload in-flight messages when client is asked to shutdown. + * Add ability to set `.integrations` field on messages. + * Fix resource leak with interval ticker after shutdown. + * Add retries and back-off when uploading messages. + * Add ability to set custom flush interval. + +v2.0.0 / 2015-02-03 +=================== + + * rewrite with breaking API changes + +v1.2.0 / 2014-09-03 +================== + + * add public .Flush() method + * rename .Stop() to .Close() + +v1.1.0 / 2014-09-02 +================== + + * add client.Stop() to flash/wait. Closes #7 + +v1.0.0 / 2014-08-26 +================== + + * fix response close + * change comments to be more go-like + * change uuid libraries + +0.1.2 / 2014-06-11 +================== + + * add runnable example + * fix: close body + +0.1.1 / 2014-05-31 +================== + + * refactor locking + +0.1.0 / 2014-05-22 +================== + + * replace Debug option with debug package + +0.0.2 / 2014-05-20 +================== + + * add .Start() + * add mutexes + * rename BufferSize to FlushAt and FlushInterval to FlushAfter + * lower FlushInterval to 5 seconds + * lower BufferSize to 20 to match other clients diff --git a/vendor/github.com/segmentio/analytics-go/Makefile b/vendor/github.com/segmentio/analytics-go/Makefile new file mode 100644 index 000000000..e896b1b5f --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/Makefile @@ -0,0 +1,10 @@ +vet: + @godep go vet ./... + +build: + @godep go build + +test: + @godep go test -race -cover ./... + +.PHONY: vet build test diff --git a/vendor/github.com/segmentio/analytics-go/Readme.md b/vendor/github.com/segmentio/analytics-go/Readme.md new file mode 100644 index 000000000..b521cbe63 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/Readme.md @@ -0,0 +1,8 @@ +# analytics-go + + Segment analytics client for Go. For additional documentation + visit [https://segment.com/docs/libraries/go](https://segment.com/docs/libraries/go/) or view the [godocs](http://godoc.org/github.com/segmentio/analytics-go). + +## License + + MIT diff --git a/vendor/github.com/segmentio/analytics-go/analytics.go b/vendor/github.com/segmentio/analytics-go/analytics.go new file mode 100644 index 000000000..6ec93fcbf --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/analytics.go @@ -0,0 +1,407 @@ +package analytics + +import ( + "fmt" + "io/ioutil" + "os" + "sync" + + "bytes" + "encoding/json" + "errors" + "log" + "net/http" + "time" + + "github.com/jehiah/go-strftime" + "github.com/segmentio/backo-go" + "github.com/xtgo/uuid" +) + +// Version of the client. +const Version = "2.1.0" + +// Endpoint for the Segment API. +const Endpoint = "https://api.segment.io" + +// DefaultContext of message batches. +var DefaultContext = map[string]interface{}{ + "library": map[string]interface{}{ + "name": "analytics-go", + "version": Version, + }, +} + +// Backoff policy. +var Backo = backo.DefaultBacko() + +// Message interface. +type message interface { + setMessageId(string) + setTimestamp(string) +} + +// Message fields common to all. +type Message struct { + Type string `json:"type,omitempty"` + MessageId string `json:"messageId,omitempty"` + Timestamp string `json:"timestamp,omitempty"` + SentAt string `json:"sentAt,omitempty"` +} + +// Batch message. +type Batch struct { + Context map[string]interface{} `json:"context,omitempty"` + Messages []interface{} `json:"batch"` + Message +} + +// Identify message. +type Identify struct { + Context map[string]interface{} `json:"context,omitempty"` + Integrations map[string]interface{} `json:"integrations,omitempty"` + Traits map[string]interface{} `json:"traits,omitempty"` + AnonymousId string `json:"anonymousId,omitempty"` + UserId string `json:"userId,omitempty"` + Message +} + +// Group message. +type Group struct { + Context map[string]interface{} `json:"context,omitempty"` + Integrations map[string]interface{} `json:"integrations,omitempty"` + Traits map[string]interface{} `json:"traits,omitempty"` + AnonymousId string `json:"anonymousId,omitempty"` + UserId string `json:"userId,omitempty"` + GroupId string `json:"groupId"` + Message +} + +// Track message. +type Track struct { + Context map[string]interface{} `json:"context,omitempty"` + Integrations map[string]interface{} `json:"integrations,omitempty"` + Properties map[string]interface{} `json:"properties,omitempty"` + AnonymousId string `json:"anonymousId,omitempty"` + UserId string `json:"userId,omitempty"` + Event string `json:"event"` + Message +} + +// Page message. +type Page struct { + Context map[string]interface{} `json:"context,omitempty"` + Integrations map[string]interface{} `json:"integrations,omitempty"` + Traits map[string]interface{} `json:"properties,omitempty"` + AnonymousId string `json:"anonymousId,omitempty"` + UserId string `json:"userId,omitempty"` + Category string `json:"category,omitempty"` + Name string `json:"name,omitempty"` + Message +} + +// Alias message. +type Alias struct { + PreviousId string `json:"previousId"` + UserId string `json:"userId"` + Message +} + +// Client which batches messages and flushes at the given Interval or +// when the Size limit is exceeded. Set Verbose to true to enable +// logging output. +type Client struct { + Endpoint string + // Interval represents the duration at which messages are flushed. It may be + // configured only before any messages are enqueued. + Interval time.Duration + Size int + Logger *log.Logger + Verbose bool + Client http.Client + key string + msgs chan interface{} + quit chan struct{} + shutdown chan struct{} + uid func() string + now func() time.Time + once sync.Once + wg sync.WaitGroup + + // These synchronization primitives are used to control how many goroutines + // are spawned by the client for uploads. + upmtx sync.Mutex + upcond sync.Cond + upcount int +} + +// New client with write key. +func New(key string) *Client { + c := &Client{ + Endpoint: Endpoint, + Interval: 5 * time.Second, + Size: 250, + Logger: log.New(os.Stderr, "segment ", log.LstdFlags), + Verbose: false, + Client: *http.DefaultClient, + key: key, + msgs: make(chan interface{}, 100), + quit: make(chan struct{}), + shutdown: make(chan struct{}), + now: time.Now, + uid: uid, + } + + c.upcond.L = &c.upmtx + return c +} + +// Alias buffers an "alias" message. +func (c *Client) Alias(msg *Alias) error { + if msg.UserId == "" { + return errors.New("You must pass a 'userId'.") + } + + if msg.PreviousId == "" { + return errors.New("You must pass a 'previousId'.") + } + + msg.Type = "alias" + c.queue(msg) + + return nil +} + +// Page buffers an "page" message. +func (c *Client) Page(msg *Page) error { + if msg.UserId == "" && msg.AnonymousId == "" { + return errors.New("You must pass either an 'anonymousId' or 'userId'.") + } + + msg.Type = "page" + c.queue(msg) + + return nil +} + +// Group buffers an "group" message. +func (c *Client) Group(msg *Group) error { + if msg.GroupId == "" { + return errors.New("You must pass a 'groupId'.") + } + + if msg.UserId == "" && msg.AnonymousId == "" { + return errors.New("You must pass either an 'anonymousId' or 'userId'.") + } + + msg.Type = "group" + c.queue(msg) + + return nil +} + +// Identify buffers an "identify" message. +func (c *Client) Identify(msg *Identify) error { + if msg.UserId == "" && msg.AnonymousId == "" { + return errors.New("You must pass either an 'anonymousId' or 'userId'.") + } + + msg.Type = "identify" + c.queue(msg) + + return nil +} + +// Track buffers an "track" message. +func (c *Client) Track(msg *Track) error { + if msg.Event == "" { + return errors.New("You must pass 'event'.") + } + + if msg.UserId == "" && msg.AnonymousId == "" { + return errors.New("You must pass either an 'anonymousId' or 'userId'.") + } + + msg.Type = "track" + c.queue(msg) + + return nil +} + +func (c *Client) startLoop() { + go c.loop() +} + +// Queue message. +func (c *Client) queue(msg message) { + c.once.Do(c.startLoop) + msg.setMessageId(c.uid()) + msg.setTimestamp(timestamp(c.now())) + c.msgs <- msg +} + +// Close and flush metrics. +func (c *Client) Close() error { + c.once.Do(c.startLoop) + c.quit <- struct{}{} + close(c.msgs) + <-c.shutdown + return nil +} + +func (c *Client) sendAsync(msgs []interface{}) { + c.upmtx.Lock() + for c.upcount == 1000 { + c.upcond.Wait() + } + c.upcount++ + c.upmtx.Unlock() + c.wg.Add(1) + go func() { + err := c.send(msgs) + if err != nil { + c.logf(err.Error()) + } + c.upmtx.Lock() + c.upcount-- + c.upcond.Signal() + c.upmtx.Unlock() + c.wg.Done() + }() +} + +// Send batch request. +func (c *Client) send(msgs []interface{}) error { + if len(msgs) == 0 { + return nil + } + + batch := new(Batch) + batch.Messages = msgs + batch.MessageId = c.uid() + batch.SentAt = timestamp(c.now()) + batch.Context = DefaultContext + + b, err := json.Marshal(batch) + if err != nil { + return fmt.Errorf("error marshalling msgs: %s", err) + } + + for i := 0; i < 10; i++ { + if err = c.upload(b); err == nil { + return nil + } + Backo.Sleep(i) + } + + return err +} + +// Upload serialized batch message. +func (c *Client) upload(b []byte) error { + url := c.Endpoint + "/v1/batch" + req, err := http.NewRequest("POST", url, bytes.NewReader(b)) + if err != nil { + return fmt.Errorf("error creating request: %s", err) + } + + req.Header.Add("User-Agent", "analytics-go (version: "+Version+")") + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Content-Length", string(len(b))) + req.SetBasicAuth(c.key, "") + + res, err := c.Client.Do(req) + if err != nil { + return fmt.Errorf("error sending request: %s", err) + } + defer res.Body.Close() + + if res.StatusCode < 400 { + c.verbose("response %s", res.Status) + return nil + } + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("error reading response body: %s", err) + } + + return fmt.Errorf("response %s: %d – %s", res.Status, res.StatusCode, string(body)) +} + +// Batch loop. +func (c *Client) loop() { + var msgs []interface{} + tick := time.NewTicker(c.Interval) + + for { + select { + case msg := <-c.msgs: + c.verbose("buffer (%d/%d) %v", len(msgs), c.Size, msg) + msgs = append(msgs, msg) + if len(msgs) == c.Size { + c.verbose("exceeded %d messages – flushing", c.Size) + c.sendAsync(msgs) + msgs = make([]interface{}, 0, c.Size) + } + case <-tick.C: + if len(msgs) > 0 { + c.verbose("interval reached - flushing %d", len(msgs)) + c.sendAsync(msgs) + msgs = make([]interface{}, 0, c.Size) + } else { + c.verbose("interval reached – nothing to send") + } + case <-c.quit: + tick.Stop() + c.verbose("exit requested – draining msgs") + // drain the msg channel. + for msg := range c.msgs { + c.verbose("buffer (%d/%d) %v", len(msgs), c.Size, msg) + msgs = append(msgs, msg) + } + c.verbose("exit requested – flushing %d", len(msgs)) + c.sendAsync(msgs) + c.wg.Wait() + c.verbose("exit") + c.shutdown <- struct{}{} + return + } + } +} + +// Verbose log. +func (c *Client) verbose(msg string, args ...interface{}) { + if c.Verbose { + c.Logger.Printf(msg, args...) + } +} + +// Unconditional log. +func (c *Client) logf(msg string, args ...interface{}) { + c.Logger.Printf(msg, args...) +} + +// Set message timestamp if one is not already set. +func (m *Message) setTimestamp(s string) { + if m.Timestamp == "" { + m.Timestamp = s + } +} + +// Set message id. +func (m *Message) setMessageId(s string) { + if m.MessageId == "" { + m.MessageId = s + } +} + +// Return formatted timestamp. +func timestamp(t time.Time) string { + return strftime.Format("%Y-%m-%dT%H:%M:%S%z", t) +} + +// Return uuid string. +func uid() string { + return uuid.NewRandom().String() +} diff --git a/vendor/github.com/segmentio/analytics-go/analytics_test.go b/vendor/github.com/segmentio/analytics-go/analytics_test.go new file mode 100644 index 000000000..c6f0dc4f2 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/analytics_test.go @@ -0,0 +1,478 @@ +package analytics + +import "net/http/httptest" +import "encoding/json" +import "net/http" +import "testing" +import "bytes" +import "time" +import "fmt" +import "io" + +func mockId() string { return "I'm unique" } + +func mockTime() time.Time { + // time.Unix(0, 0) fails on Circle + return time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) +} + +func mockServer() (chan []byte, *httptest.Server) { + done := make(chan []byte, 1) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + buf := bytes.NewBuffer(nil) + io.Copy(buf, r.Body) + + var v interface{} + err := json.Unmarshal(buf.Bytes(), &v) + if err != nil { + panic(err) + } + + b, err := json.MarshalIndent(v, "", " ") + if err != nil { + panic(err) + } + + done <- b + })) + + return done, server +} + +func ExampleTrack() { + body, server := mockServer() + defer server.Close() + + client := New("h97jamjwbh") + client.Endpoint = server.URL + client.now = mockTime + client.uid = mockId + client.Size = 1 + + client.Track(&Track{ + Event: "Download", + UserId: "123456", + Properties: map[string]interface{}{ + "application": "Segment Desktop", + "version": "1.1.0", + "platform": "osx", + }, + }) + + fmt.Printf("%s\n", <-body) + // Output: + // { + // "batch": [ + // { + // "event": "Download", + // "messageId": "I'm unique", + // "properties": { + // "application": "Segment Desktop", + // "platform": "osx", + // "version": "1.1.0" + // }, + // "timestamp": "2009-11-10T23:00:00+0000", + // "type": "track", + // "userId": "123456" + // } + // ], + // "context": { + // "library": { + // "name": "analytics-go", + // "version": "2.1.0" + // } + // }, + // "messageId": "I'm unique", + // "sentAt": "2009-11-10T23:00:00+0000" + // } +} + +func ExampleClose() { + body, server := mockServer() + defer server.Close() + + client := New("h97jamjwbh") + client.Endpoint = server.URL + client.now = mockTime + client.uid = mockId + + client.Track(&Track{ + Event: "Download", + UserId: "123456", + Properties: map[string]interface{}{ + "application": "Segment Desktop", + "version": "1.1.0", + "platform": "osx", + }, + }) + + client.Close() + + fmt.Printf("%s\n", <-body) + // Output: + // { + // "batch": [ + // { + // "event": "Download", + // "messageId": "I'm unique", + // "properties": { + // "application": "Segment Desktop", + // "platform": "osx", + // "version": "1.1.0" + // }, + // "timestamp": "2009-11-10T23:00:00+0000", + // "type": "track", + // "userId": "123456" + // } + // ], + // "context": { + // "library": { + // "name": "analytics-go", + // "version": "2.1.0" + // } + // }, + // "messageId": "I'm unique", + // "sentAt": "2009-11-10T23:00:00+0000" + // } +} + +func ExampleInterval() { + body, server := mockServer() + defer server.Close() + + client := New("h97jamjwbh") + client.Endpoint = server.URL + client.now = mockTime + client.uid = mockId + + client.Track(&Track{ + Event: "Download", + UserId: "123456", + Properties: map[string]interface{}{ + "application": "Segment Desktop", + "version": "1.1.0", + "platform": "osx", + }, + }) + + // Will flush in 5 seconds (default interval). + fmt.Printf("%s\n", <-body) + // Output: + // { + // "batch": [ + // { + // "event": "Download", + // "messageId": "I'm unique", + // "properties": { + // "application": "Segment Desktop", + // "platform": "osx", + // "version": "1.1.0" + // }, + // "timestamp": "2009-11-10T23:00:00+0000", + // "type": "track", + // "userId": "123456" + // } + // ], + // "context": { + // "library": { + // "name": "analytics-go", + // "version": "2.1.0" + // } + // }, + // "messageId": "I'm unique", + // "sentAt": "2009-11-10T23:00:00+0000" + // } +} + +func ExampleTrackWithTimestampSet() { + body, server := mockServer() + defer server.Close() + + client := New("h97jamjwbh") + client.Endpoint = server.URL + client.now = mockTime + client.uid = mockId + client.Size = 1 + + client.Track(&Track{ + Event: "Download", + UserId: "123456", + Properties: map[string]interface{}{ + "application": "Segment Desktop", + "version": "1.1.0", + "platform": "osx", + }, + Message: Message{ + Timestamp: timestamp(time.Date(2015, time.July, 10, 23, 0, 0, 0, time.UTC)), + }, + }) + + fmt.Printf("%s\n", <-body) + // Output: + // { + // "batch": [ + // { + // "event": "Download", + // "messageId": "I'm unique", + // "properties": { + // "application": "Segment Desktop", + // "platform": "osx", + // "version": "1.1.0" + // }, + // "timestamp": "2015-07-10T23:00:00+0000", + // "type": "track", + // "userId": "123456" + // } + // ], + // "context": { + // "library": { + // "name": "analytics-go", + // "version": "2.1.0" + // } + // }, + // "messageId": "I'm unique", + // "sentAt": "2009-11-10T23:00:00+0000" + // } +} + +func ExampleTrackWithMessageIdSet() { + body, server := mockServer() + defer server.Close() + + client := New("h97jamjwbh") + client.Endpoint = server.URL + client.now = mockTime + client.uid = mockId + client.Size = 1 + + client.Track(&Track{ + Event: "Download", + UserId: "123456", + Properties: map[string]interface{}{ + "application": "Segment Desktop", + "version": "1.1.0", + "platform": "osx", + }, + Message: Message{ + MessageId: "abc", + }, + }) + + fmt.Printf("%s\n", <-body) + // Output: + // { + // "batch": [ + // { + // "event": "Download", + // "messageId": "abc", + // "properties": { + // "application": "Segment Desktop", + // "platform": "osx", + // "version": "1.1.0" + // }, + // "timestamp": "2009-11-10T23:00:00+0000", + // "type": "track", + // "userId": "123456" + // } + // ], + // "context": { + // "library": { + // "name": "analytics-go", + // "version": "2.1.0" + // } + // }, + // "messageId": "I'm unique", + // "sentAt": "2009-11-10T23:00:00+0000" + // } +} + +func ExampleTrack_context() { + body, server := mockServer() + defer server.Close() + + client := New("h97jamjwbh") + client.Endpoint = server.URL + client.now = mockTime + client.uid = mockId + client.Size = 1 + + client.Track(&Track{ + Event: "Download", + UserId: "123456", + Properties: map[string]interface{}{ + "application": "Segment Desktop", + "version": "1.1.0", + "platform": "osx", + }, + Context: map[string]interface{}{ + "whatever": "here", + }, + }) + + fmt.Printf("%s\n", <-body) + // Output: + // { + // "batch": [ + // { + // "context": { + // "whatever": "here" + // }, + // "event": "Download", + // "messageId": "I'm unique", + // "properties": { + // "application": "Segment Desktop", + // "platform": "osx", + // "version": "1.1.0" + // }, + // "timestamp": "2009-11-10T23:00:00+0000", + // "type": "track", + // "userId": "123456" + // } + // ], + // "context": { + // "library": { + // "name": "analytics-go", + // "version": "2.1.0" + // } + // }, + // "messageId": "I'm unique", + // "sentAt": "2009-11-10T23:00:00+0000" + // } +} + +func ExampleTrack_many() { + body, server := mockServer() + defer server.Close() + + client := New("h97jamjwbh") + client.Endpoint = server.URL + client.now = mockTime + client.uid = mockId + client.Size = 3 + + for i := 0; i < 5; i++ { + client.Track(&Track{ + Event: "Download", + UserId: "123456", + Properties: map[string]interface{}{ + "application": "Segment Desktop", + "version": i, + }, + }) + } + + fmt.Printf("%s\n", <-body) + // Output: + // { + // "batch": [ + // { + // "event": "Download", + // "messageId": "I'm unique", + // "properties": { + // "application": "Segment Desktop", + // "version": 0 + // }, + // "timestamp": "2009-11-10T23:00:00+0000", + // "type": "track", + // "userId": "123456" + // }, + // { + // "event": "Download", + // "messageId": "I'm unique", + // "properties": { + // "application": "Segment Desktop", + // "version": 1 + // }, + // "timestamp": "2009-11-10T23:00:00+0000", + // "type": "track", + // "userId": "123456" + // }, + // { + // "event": "Download", + // "messageId": "I'm unique", + // "properties": { + // "application": "Segment Desktop", + // "version": 2 + // }, + // "timestamp": "2009-11-10T23:00:00+0000", + // "type": "track", + // "userId": "123456" + // } + // ], + // "context": { + // "library": { + // "name": "analytics-go", + // "version": "2.1.0" + // } + // }, + // "messageId": "I'm unique", + // "sentAt": "2009-11-10T23:00:00+0000" + // } +} + +func ExampleTrackWithIntegrations() { + body, server := mockServer() + defer server.Close() + + client := New("h97jamjwbh") + client.Endpoint = server.URL + client.now = mockTime + client.uid = mockId + client.Size = 1 + + client.Track(&Track{ + Event: "Download", + UserId: "123456", + Properties: map[string]interface{}{ + "application": "Segment Desktop", + "version": "1.1.0", + "platform": "osx", + }, + Integrations: map[string]interface{}{ + "All": true, + "Intercom": false, + "Mixpanel": true, + }, + }) + + fmt.Printf("%s\n", <-body) + // Output: + // { + // "batch": [ + // { + // "event": "Download", + // "integrations": { + // "All": true, + // "Intercom": false, + // "Mixpanel": true + // }, + // "messageId": "I'm unique", + // "properties": { + // "application": "Segment Desktop", + // "platform": "osx", + // "version": "1.1.0" + // }, + // "timestamp": "2009-11-10T23:00:00+0000", + // "type": "track", + // "userId": "123456" + // } + // ], + // "context": { + // "library": { + // "name": "analytics-go", + // "version": "2.1.0" + // } + // }, + // "messageId": "I'm unique", + // "sentAt": "2009-11-10T23:00:00+0000" + // } +} + +// Tests that calling Close right after creating the client object doesn't +// block. +// Bug: https://github.com/segmentio/analytics-go/issues/43 +func TestCloseFinish(_ *testing.T) { + c := New("test") + c.Close() +} diff --git a/vendor/github.com/segmentio/analytics-go/circle.yml b/vendor/github.com/segmentio/analytics-go/circle.yml new file mode 100644 index 000000000..11733e196 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/circle.yml @@ -0,0 +1,18 @@ +machine: + environment: + IMPORT_PATH: "github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME" + +dependencies: + pre: + - go get github.com/tools/godep + + override: + - mkdir -p "$GOPATH/src/$IMPORT_PATH" + - rsync -azC --delete ./ "$GOPATH/src/$IMPORT_PATH/" + +test: + pre: + - make vet + + override: + - make test diff --git a/vendor/github.com/segmentio/analytics-go/cli/cli.go b/vendor/github.com/segmentio/analytics-go/cli/cli.go new file mode 100644 index 000000000..9d1f940c9 --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/cli/cli.go @@ -0,0 +1,174 @@ +package main + +import ( + "encoding/json" + "log" + "os" + "reflect" + + "github.com/segmentio/analytics-go" + "github.com/tj/docopt" +) + +const Usage = ` +Analytics Go CLI + +Usage: + analytics track [--properties=] [--context=] [--writeKey=] [--userId=] [--anonymousId=] [--integrations=] [--timestamp=] + analytics screen [--properties=] [--context=] [--writeKey=] [--userId=] [--anonymousId=] [--integrations=] [--timestamp=] + analytics page [--properties=] [--context=] [--writeKey=] [--userId=] [--anonymousId=] [--integrations=] [--timestamp=] + analytics identify [--traits=] [--context=] [--writeKey=] [--userId=] [--anonymousId=] [--integrations=] [--timestamp=] + analytics group --groupId= [--traits=] [--properties=] [--context=] [--writeKey=] [--userId=] [--anonymousId=] [--integrations=] [--timestamp=] + analytics alias --userId= --previousId= [--traits=] [--properties=] [--context=] [--writeKey=] [--anonymousId=] [--integrations=] [--timestamp=] + analytics -h | --help + analytics --version + +Options: + -h --help Show this screen. + --version Show version. +` + +func main() { + arguments, err := docopt.Parse(Usage, nil, true, "Anaytics Go CLI", false) + check(err) + + writeKey := getOptionalString(arguments, "--writeKey") + if writeKey == "" { + writeKey = os.Getenv("SEGMENT_WRITE_KEY") + if writeKey == "" { + log.Fatal("either $SEGMENT_WRITE_KEY or --writeKey must be provided") + } + } + + client := analytics.New(writeKey) + client.Size = 1 + client.Verbose = true + + if arguments["track"].(bool) { + m := &analytics.Track{ + Event: arguments[""].(string), + } + properties := getOptionalString(arguments, "--properties") + if properties != "" { + var parsedProperties map[string]interface{} + err := json.Unmarshal([]byte(properties), &parsedProperties) + check(err) + m.Properties = parsedProperties + } + + setCommonFields(m, arguments) + + check(client.Track(m)) + } + + if arguments["screen"].(bool) || arguments["page"].(bool) { + m := &analytics.Page{ + Name: arguments[""].(string), + } + /* Bug in Go library - page has traits not properties. + properties := getOptionalString(arguments, "--properties") + if properties != "" { + var parsedProperties map[string]interface{} + err := json.Unmarshal([]byte(properties), &parsedProperties) + check(err) + t.Properties = parsedProperties + } + */ + + setCommonFields(m, arguments) + + check(client.Page(m)) + } + + if arguments["identify"].(bool) { + m := &analytics.Identify{} + traits := getOptionalString(arguments, "--traits") + if traits != "" { + var parsedTraits map[string]interface{} + err := json.Unmarshal([]byte(traits), &parsedTraits) + check(err) + m.Traits = parsedTraits + } + + setCommonFields(m, arguments) + + check(client.Identify(m)) + } + + if arguments["group"].(bool) { + m := &analytics.Group{ + GroupId: arguments["--groupId"].(string), + } + traits := getOptionalString(arguments, "--traits") + if traits != "" { + var parsedTraits map[string]interface{} + err := json.Unmarshal([]byte(traits), &parsedTraits) + check(err) + m.Traits = parsedTraits + } + + setCommonFields(m, arguments) + + check(client.Group(m)) + } + + if arguments["alias"].(bool) { + m := &analytics.Alias{ + PreviousId: arguments["--previousId"].(string), + } + + setCommonFields(m, arguments) + + check(client.Alias(m)) + } + + client.Close() +} + +func setCommonFields(message interface{}, arguments map[string]interface{}) { + userId := getOptionalString(arguments, "--userId") + if userId != "" { + setFieldValue(message, "UserId", userId) + } + anonymousId := getOptionalString(arguments, "--anonymousId") + if anonymousId != "" { + setFieldValue(message, "AnonymousId", anonymousId) + } + integrations := getOptionalString(arguments, "--integrations") + if integrations != "" { + var parsedIntegrations map[string]interface{} + err := json.Unmarshal([]byte(integrations), &parsedIntegrations) + check(err) + setFieldValue(message, "Integrations", parsedIntegrations) + } + context := getOptionalString(arguments, "--context") + if context != "" { + var parsedContext map[string]interface{} + err := json.Unmarshal([]byte(context), &parsedContext) + check(err) + setFieldValue(message, "Context", parsedContext) + + } + timestamp := getOptionalString(arguments, "--timestamp") + if timestamp != "" { + setFieldValue(message, "Timestamp", timestamp) + } +} + +func setFieldValue(target interface{}, field string, value interface{}) { + reflect.ValueOf(target).Elem().FieldByName(field).Set(reflect.ValueOf(value)) +} + +func getOptionalString(m map[string]interface{}, k string) string { + v := m[k] + if v == nil { + return "" + } + return v.(string) +} + +func check(err error) { + if err != nil { + log.Fatal(err) + } +} diff --git a/vendor/github.com/segmentio/analytics-go/examples/track.go b/vendor/github.com/segmentio/analytics-go/examples/track.go new file mode 100644 index 000000000..484c38efa --- /dev/null +++ b/vendor/github.com/segmentio/analytics-go/examples/track.go @@ -0,0 +1,36 @@ +package main + +import "github.com/segmentio/analytics-go" +import "time" + +func main() { + client := analytics.New("h97jamjwbh") + client.Interval = 30 * time.Second + client.Size = 100 + client.Verbose = true + + done := time.After(3 * time.Second) + tick := time.Tick(50 * time.Millisecond) + +out: + for { + select { + case <-done: + println("exiting") + break out + case <-tick: + client.Track(&analytics.Track{ + Event: "Download", + UserId: "123456", + Properties: map[string]interface{}{ + "application": "Segment Desktop", + "version": "1.1.0", + "platform": "osx", + }, + }) + } + } + + println("flushing") + client.Close() +} diff --git a/vendor/github.com/segmentio/backo-go/README.md b/vendor/github.com/segmentio/backo-go/README.md new file mode 100644 index 000000000..1362becf1 --- /dev/null +++ b/vendor/github.com/segmentio/backo-go/README.md @@ -0,0 +1,80 @@ +Backo [![GoDoc](http://godoc.org/github.com/segmentio/backo-go?status.png)](http://godoc.org/github.com/segmentio/backo-go) +----- + +Exponential backoff for Go (Go port of segmentio/backo). + + +Usage +----- + +```go +import "github.com/segmentio/backo-go" + +// Create a Backo instance. +backo := backo.NewBacko(milliseconds(100), 2, 1, milliseconds(10*1000)) +// OR with defaults. +backo := backo.DefaultBacko() + +// Use the ticker API. +ticker := b.NewTicker() +for { + timeout := time.After(5 * time.Minute) + select { + case <-ticker.C: + fmt.Println("ticked") + case <- timeout: + fmt.Println("timed out") + } +} + +// Or simply work with backoff intervals directly. +for i := 0; i < n; i++ { + // Sleep the current goroutine. + backo.Sleep(i) + // Retrieve the duration manually. + duration := backo.Duration(i) +} +``` + +License +------- + +``` +WWWWWW||WWWWWW + W W W||W W W + || + ( OO )__________ + / | \ + /o o| MIT \ + \___/||_||__||_|| * + || || || || + _||_|| _||_|| + (__|__|(__|__| + +The MIT License (MIT) + +Copyright (c) 2015 Segment, Inc. + +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. +``` + + + + [1]: http://github.com/segmentio/backo-java + [2]: http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.segment.backo&a=backo&v=LATEST \ No newline at end of file diff --git a/vendor/github.com/segmentio/backo-go/backo.go b/vendor/github.com/segmentio/backo-go/backo.go new file mode 100644 index 000000000..6f7b6d5e2 --- /dev/null +++ b/vendor/github.com/segmentio/backo-go/backo.go @@ -0,0 +1,83 @@ +package backo + +import ( + "math" + "math/rand" + "time" +) + +type Backo struct { + base time.Duration + factor uint8 + jitter float64 + cap time.Duration +} + +// Creates a backo instance with the given parameters +func NewBacko(base time.Duration, factor uint8, jitter float64, cap time.Duration) *Backo { + return &Backo{base, factor, jitter, cap} +} + +// Creates a backo instance with the following defaults: +// base: 100 milliseconds +// factor: 2 +// jitter: 0 +// cap: 10 seconds +func DefaultBacko() *Backo { + return NewBacko(time.Millisecond*100, 2, 0, time.Second*10) +} + +// Duration returns the backoff interval for the given attempt. +func (backo *Backo) Duration(attempt int) time.Duration { + duration := float64(backo.base) * math.Pow(float64(backo.factor), float64(attempt)) + + if backo.jitter != 0 { + random := rand.Float64() + deviation := math.Floor(random * backo.jitter * duration) + if (int(math.Floor(random*10)) & 1) == 0 { + duration = duration - deviation + } else { + duration = duration + deviation + } + } + + duration = math.Min(float64(duration), float64(backo.cap)) + return time.Duration(duration) +} + +// Sleep pauses the current goroutine for the backoff interval for the given attempt. +func (backo *Backo) Sleep(attempt int) { + duration := backo.Duration(attempt) + time.Sleep(duration) +} + +type Ticker struct { + done chan struct{} + C <-chan time.Time +} + +func (b *Backo) NewTicker() *Ticker { + c := make(chan time.Time, 1) + ticker := &Ticker{ + done: make(chan struct{}, 1), + C: c, + } + + go func() { + for i := 0; ; i++ { + select { + case t := <-time.After(b.Duration(i)): + c <- t + case <-ticker.done: + close(c) + return + } + } + }() + + return ticker +} + +func (t *Ticker) Stop() { + t.done <- struct{}{} +} diff --git a/vendor/github.com/segmentio/backo-go/backo_test.go b/vendor/github.com/segmentio/backo-go/backo_test.go new file mode 100644 index 000000000..89933acf7 --- /dev/null +++ b/vendor/github.com/segmentio/backo-go/backo_test.go @@ -0,0 +1,77 @@ +package backo + +import ( + "fmt" + "math" + "testing" + "time" + + "github.com/bmizerany/assert" +) + +// Tests default backo behaviour. +func TestDefaults(t *testing.T) { + backo := DefaultBacko() + + assert.Equal(t, milliseconds(100), backo.Duration(0)) + assert.Equal(t, milliseconds(200), backo.Duration(1)) + assert.Equal(t, milliseconds(400), backo.Duration(2)) + assert.Equal(t, milliseconds(800), backo.Duration(3)) +} + +// Tests backo does not exceed cap. +func TestCap(t *testing.T) { + backo := NewBacko(milliseconds(100), 2, 0, milliseconds(600)) + + assert.Equal(t, milliseconds(100), backo.Duration(0)) + assert.Equal(t, milliseconds(200), backo.Duration(1)) + assert.Equal(t, milliseconds(400), backo.Duration(2)) + assert.Equal(t, milliseconds(600), backo.Duration(3)) +} + +// Tests that jitter adds randomness. +func TestJitter(t *testing.T) { + defaultBacko := NewBacko(milliseconds(100), 2, 1, milliseconds(10*1000)) + jitterBacko := NewBacko(milliseconds(100), 2, 1, milliseconds(10*1000)) + + // TODO: Check jittered durations are within a range. + assert.NotEqual(t, jitterBacko.Duration(0), defaultBacko.Duration(0)) + assert.NotEqual(t, jitterBacko.Duration(1), defaultBacko.Duration(1)) + assert.NotEqual(t, jitterBacko.Duration(2), defaultBacko.Duration(2)) + assert.NotEqual(t, jitterBacko.Duration(3), defaultBacko.Duration(3)) +} + +func ExampleBacko_BackoffDefault() { + b := DefaultBacko() + ticker := b.NewTicker() + + for i := 0; i < 6; i++ { + start := time.Now() + select { + case t := <-ticker.C: + fmt.Println(nearest10Millis(t.Sub(start))) + } + } + + ticker.Stop() + + // Output: + // 100 + // 200 + // 400 + // 800 + // 1600 + // 3200 +} + +func nearest10Millis(d time.Duration) float64 { + // Typically d is something like 11 or 21, so do some magic to round the + // durations to the nearest 10. We divide d by 10, floor it, and multiply it + // by 10 again. + return math.Floor(float64(d/time.Millisecond/10) * 10) +} + +// Returns the given milliseconds as time.Duration +func milliseconds(ms int64) time.Duration { + return time.Duration(ms * 1000 * 1000) +} diff --git a/vendor/github.com/xtgo/uuid/AUTHORS b/vendor/github.com/xtgo/uuid/AUTHORS new file mode 100644 index 000000000..a6f045160 --- /dev/null +++ b/vendor/github.com/xtgo/uuid/AUTHORS @@ -0,0 +1,5 @@ +# This source file refers to The gocql Authors for copyright purposes. + +Christoph Hack +Jonathan Rudenberg +Thorsten von Eicken diff --git a/vendor/github.com/xtgo/uuid/LICENSE b/vendor/github.com/xtgo/uuid/LICENSE new file mode 100644 index 000000000..18d25f923 --- /dev/null +++ b/vendor/github.com/xtgo/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The gocql Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/xtgo/uuid/uuid.go b/vendor/github.com/xtgo/uuid/uuid.go new file mode 100644 index 000000000..a0fd7a5a5 --- /dev/null +++ b/vendor/github.com/xtgo/uuid/uuid.go @@ -0,0 +1,204 @@ +// Copyright (c) 2012 The gocql Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package uuid can be used to generate and parse universally unique +// identifiers, a standardized format in the form of a 128 bit number. +// +// http://tools.ietf.org/html/rfc4122 +package uuid + +import ( + "crypto/rand" + "encoding/hex" + "errors" + "io" + "net" + "strconv" + "time" +) + +type UUID [16]byte + +var hardwareAddr []byte + +const ( + VariantNCSCompat = 0 + VariantIETF = 2 + VariantMicrosoft = 6 + VariantFuture = 7 +) + +func init() { + if interfaces, err := net.Interfaces(); err == nil { + for _, i := range interfaces { + if i.Flags&net.FlagLoopback == 0 && len(i.HardwareAddr) > 0 { + hardwareAddr = i.HardwareAddr + break + } + } + } + if hardwareAddr == nil { + // If we failed to obtain the MAC address of the current computer, + // we will use a randomly generated 6 byte sequence instead and set + // the multicast bit as recommended in RFC 4122. + hardwareAddr = make([]byte, 6) + _, err := io.ReadFull(rand.Reader, hardwareAddr) + if err != nil { + panic(err) + } + hardwareAddr[0] = hardwareAddr[0] | 0x01 + } +} + +// Parse parses a 32 digit hexadecimal number (that might contain hyphens) +// representing an UUID. +func Parse(input string) (UUID, error) { + var u UUID + j := 0 + for i := 0; i < len(input); i++ { + b := input[i] + switch { + default: + fallthrough + case j == 32: + goto err + case b == '-': + continue + case '0' <= b && b <= '9': + b -= '0' + case 'a' <= b && b <= 'f': + b -= 'a' - 10 + case 'A' <= b && b <= 'F': + b -= 'A' - 10 + } + u[j/2] |= b << byte(^j&1<<2) + j++ + } + if j == 32 { + return u, nil + } +err: + return UUID{}, errors.New("invalid UUID " + strconv.Quote(input)) +} + +// FromBytes converts a raw byte slice to an UUID. It will panic if the slice +// isn't exactly 16 bytes long. +func FromBytes(input []byte) UUID { + var u UUID + if len(input) != 16 { + panic("UUIDs must be exactly 16 bytes long") + } + copy(u[:], input) + return u +} + +// NewRandom generates a totally random UUID (version 4) as described in +// RFC 4122. +func NewRandom() UUID { + var u UUID + io.ReadFull(rand.Reader, u[:]) + u[6] &= 0x0F // clear version + u[6] |= 0x40 // set version to 4 (random uuid) + u[8] &= 0x3F // clear variant + u[8] |= 0x80 // set to IETF variant + return u +} + +var timeBase = time.Date(1582, time.October, 15, 0, 0, 0, 0, time.UTC).Unix() + +// NewTime generates a new time based UUID (version 1) as described in RFC +// 4122. This UUID contains the MAC address of the node that generated the +// UUID, a timestamp and a sequence number. +func NewTime() UUID { + var u UUID + + now := time.Now().In(time.UTC) + t := uint64(now.Unix()-timeBase)*10000000 + uint64(now.Nanosecond()/100) + u[0], u[1], u[2], u[3] = byte(t>>24), byte(t>>16), byte(t>>8), byte(t) + u[4], u[5] = byte(t>>40), byte(t>>32) + u[6], u[7] = byte(t>>56)&0x0F, byte(t>>48) + + var clockSeq [2]byte + io.ReadFull(rand.Reader, clockSeq[:]) + u[8] = clockSeq[1] + u[9] = clockSeq[0] + + copy(u[10:], hardwareAddr) + + u[6] |= 0x10 // set version to 1 (time based uuid) + u[8] &= 0x3F // clear variant + u[8] |= 0x80 // set to IETF variant + + return u +} + +// String returns the UUID in it's canonical form, a 32 digit hexadecimal +// number in the form of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. +func (u UUID) String() string { + buf := [36]byte{8: '-', 13: '-', 18: '-', 23: '-'} + hex.Encode(buf[0:], u[0:4]) + hex.Encode(buf[9:], u[4:6]) + hex.Encode(buf[14:], u[6:8]) + hex.Encode(buf[19:], u[8:10]) + hex.Encode(buf[24:], u[10:]) + return string(buf[:]) +} + +// Bytes returns the raw byte slice for this UUID. A UUID is always 128 bits +// (16 bytes) long. +func (u UUID) Bytes() []byte { + return u[:] +} + +// Variant returns the variant of this UUID. This package will only generate +// UUIDs in the IETF variant. +func (u UUID) Variant() int { + x := u[8] + switch byte(0) { + case x & 0x80: + return VariantNCSCompat + case x & 0x40: + return VariantIETF + case x & 0x20: + return VariantMicrosoft + } + return VariantFuture +} + +// Version extracts the version of this UUID variant. The RFC 4122 describes +// five kinds of UUIDs. +func (u UUID) Version() int { + return int(u[6] & 0xF0 >> 4) +} + +// Node extracts the MAC address of the node who generated this UUID. It will +// return nil if the UUID is not a time based UUID (version 1). +func (u UUID) Node() []byte { + if u.Version() != 1 { + return nil + } + return u[10:] +} + +// Timestamp extracts the timestamp information from a time based UUID +// (version 1). +func (u UUID) Timestamp() uint64 { + if u.Version() != 1 { + return 0 + } + return uint64(u[0])<<24 + uint64(u[1])<<16 + uint64(u[2])<<8 + + uint64(u[3]) + uint64(u[4])<<40 + uint64(u[5])<<32 + + uint64(u[7])<<48 + uint64(u[6]&0x0F)<<56 +} + +// Time is like Timestamp, except that it returns a time.Time. +func (u UUID) Time() time.Time { + t := u.Timestamp() + if t == 0 { + return time.Time{} + } + sec := t / 10000000 + nsec := t - sec + return time.Unix(int64(sec)+timeBase, int64(nsec)) +} diff --git a/vendor/github.com/xtgo/uuid/uuid_test.go b/vendor/github.com/xtgo/uuid/uuid_test.go new file mode 100644 index 000000000..3cef4a31a --- /dev/null +++ b/vendor/github.com/xtgo/uuid/uuid_test.go @@ -0,0 +1,102 @@ +// Copyright (c) 2012 The gocql Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "testing" +) + +func TestNil(t *testing.T) { + var uuid UUID + want, got := "00000000-0000-0000-0000-000000000000", uuid.String() + if want != got { + t.Fatalf("TestNil: expected %q got %q", want, got) + } +} + +var tests = []struct { + input string + variant int + version int +}{ + {"b4f00409-cef8-4822-802c-deb20704c365", VariantIETF, 4}, + {"f81d4fae-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1}, + {"00000000-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1}, + {"3051a8d7-aea7-1801-e0bf-bc539dd60cf3", VariantFuture, 1}, + {"3051a8d7-aea7-2801-e0bf-bc539dd60cf3", VariantFuture, 2}, + {"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 3}, + {"3051a8d7-aea7-4801-e0bf-bc539dd60cf3", VariantFuture, 4}, + {"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 5}, + {"d0e817e1-e4b1-1801-3fe6-b4b60ccecf9d", VariantNCSCompat, 0}, + {"d0e817e1-e4b1-1801-bfe6-b4b60ccecf9d", VariantIETF, 1}, + {"d0e817e1-e4b1-1801-dfe6-b4b60ccecf9d", VariantMicrosoft, 0}, + {"d0e817e1-e4b1-1801-ffe6-b4b60ccecf9d", VariantFuture, 0}, +} + +func TestPredefined(t *testing.T) { + for i := range tests { + uuid, err := Parse(tests[i].input) + if err != nil { + t.Errorf("Parse #%d: %v", i, err) + continue + } + + if str := uuid.String(); str != tests[i].input { + t.Errorf("String #%d: expected %q got %q", i, tests[i].input, str) + continue + } + + if variant := uuid.Variant(); variant != tests[i].variant { + t.Errorf("Variant #%d: expected %d got %d", i, tests[i].variant, variant) + } + + if tests[i].variant == VariantIETF { + if version := uuid.Version(); version != tests[i].version { + t.Errorf("Version #%d: expected %d got %d", i, tests[i].version, version) + } + } + } +} + +func TestNewRandom(t *testing.T) { + for i := 0; i < 20; i++ { + uuid := NewRandom() + + if variant := uuid.Variant(); variant != VariantIETF { + t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant) + } + if version := uuid.Version(); version != 4 { + t.Errorf("wrong version. expected %d got %d", 4, version) + } + } +} + +func TestNewTime(t *testing.T) { + var node []byte + timestamp := uint64(0) + for i := 0; i < 20; i++ { + uuid := NewTime() + + if variant := uuid.Variant(); variant != VariantIETF { + t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant) + } + if version := uuid.Version(); version != 1 { + t.Errorf("wrong version. expected %d got %d", 1, version) + } + + if n := uuid.Node(); !bytes.Equal(n, node) && i > 0 { + t.Errorf("wrong node. expected %x, got %x", node, n) + } else if i == 0 { + node = n + } + + ts := uuid.Timestamp() + if ts < timestamp { + t.Errorf("timestamps must grow") + } + timestamp = ts + } +} -- cgit v1.2.3-1-g7c22