diff --git a/.gitignore b/.gitignore
index d9a4482..53e2fed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,4 +21,3 @@ stage
*.snap
*bfg-report*
*bfg-report
-vendor
diff --git a/Gopkg.lock b/Gopkg.lock
index bae54ed..11e681f 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -13,24 +13,12 @@
revision = "cd07662ec9300d16d2ece1c24b0b29ad89f65ff4"
version = "v1.1"
-[[projects]]
- branch = "master"
- name = "github.com/bradfitz/slice"
- packages = ["."]
- revision = "d9036e2120b5ddfa53f3ebccd618c4af275f47da"
-
[[projects]]
name = "github.com/fatih/color"
packages = ["."]
revision = "507f6050b8568533fb3f5504de8e5205fa62a114"
version = "v1.6.0"
-[[projects]]
- branch = "master"
- name = "github.com/jroimartin/gocui"
- packages = ["."]
- revision = "c055c87ae801372cd74a0839b972db4f7697ae5f"
-
[[projects]]
name = "github.com/mattn/go-colorable"
packages = ["."]
@@ -53,19 +41,13 @@
branch = "master"
name = "github.com/miguelmota/go-coinmarketcap"
packages = ["."]
- revision = "824d58bf0ced24a3c5211695d1da4751c179ab64"
+ revision = "42269f499a7031e0affa526258909fa73f2914cd"
[[projects]]
branch = "master"
name = "github.com/nsf/termbox-go"
packages = ["."]
- revision = "3e24a7b6661e09b87a9f49d693034219f81602fa"
-
-[[projects]]
- name = "github.com/patrickmn/go-cache"
- packages = ["."]
- revision = "a3647f8e31d79543b2d0f0ae2fe5c379d72cedc0"
- version = "v2.1.0"
+ revision = "7cbfaac9e282b3ea0cefeddc67b2c3ed3aaf97bc"
[[projects]]
branch = "master"
@@ -80,17 +62,17 @@
"html",
"html/atom"
]
- revision = "84348c2dc81a524fe94aaea3e3d9f967135338ef"
+ revision = "5f9ae10d9af5b1c89ae6904293b14b064d4ada23"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
- revision = "b126b21c05a91c856b027c16779c12e3bf236954"
+ revision = "bb9c189858d91f42db229b04d45a4c3d23a7662a"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "a379741758ab87277a2159f17b7f9741ece58d2f94b1c959191189f9100a6f29"
+ inputs-digest = "f8fa24cf45636d4d6991f0a805aba5b6ad13ce7807be80cc3baf17909e6b855b"
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
index 5218fdc..8738f07 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -25,30 +25,12 @@
# unused-packages = true
-[[constraint]]
- branch = "master"
- name = "github.com/bradfitz/slice"
[[constraint]]
name = "github.com/fatih/color"
version = "1.6.0"
-[[constraint]]
- name = "github.com/gizak/termui"
- version = "2.2.0"
-
-[[constraint]]
- name = "github.com/jroimartin/gocui"
- branch = "master"
-
[[constraint]]
branch = "master"
name = "github.com/miguelmota/go-coinmarketcap"
-[prune]
- go-tests = true
- unused-packages = true
-
-[[constraint]]
- name = "github.com/patrickmn/go-cache"
- version = "2.1.0"
diff --git a/cointop/cointop.go b/cointop/cointop.go
index fb7c5da..26f2fde 100644
--- a/cointop/cointop.go
+++ b/cointop/cointop.go
@@ -6,11 +6,11 @@ import (
"sync"
"time"
- "github.com/jroimartin/gocui"
"github.com/miguelmota/cointop/pkg/api"
+ "github.com/miguelmota/cointop/pkg/cache"
+ "github.com/miguelmota/cointop/pkg/gocui"
"github.com/miguelmota/cointop/pkg/table"
"github.com/miguelmota/cointop/pkg/termui"
- "github.com/patrickmn/go-cache"
)
// Cointop cointop
diff --git a/cointop/keybindings.go b/cointop/keybindings.go
index 1188d6e..51acc87 100644
--- a/cointop/keybindings.go
+++ b/cointop/keybindings.go
@@ -3,7 +3,7 @@ package cointop
import (
"strings"
- "github.com/jroimartin/gocui"
+ "github.com/miguelmota/cointop/pkg/gocui"
)
func (ct *Cointop) parseKeys(s string) (interface{}, gocui.Modifier) {
diff --git a/cointop/layout.go b/cointop/layout.go
index 098e562..d7a9333 100644
--- a/cointop/layout.go
+++ b/cointop/layout.go
@@ -3,7 +3,7 @@ package cointop
import (
"fmt"
- "github.com/jroimartin/gocui"
+ "github.com/miguelmota/cointop/pkg/gocui"
)
func (ct *Cointop) layout(g *gocui.Gui) error {
diff --git a/cointop/sort.go b/cointop/sort.go
index de506a6..8053340 100644
--- a/cointop/sort.go
+++ b/cointop/sort.go
@@ -1,8 +1,8 @@
package cointop
import (
- "github.com/bradfitz/slice"
- "github.com/jroimartin/gocui"
+ "github.com/miguelmota/cointop/pkg/gocui"
+ "github.com/miguelmota/cointop/pkg/slice"
)
var colorder = []string{
diff --git a/cointop/update.go b/cointop/update.go
index 049a3c9..9e81ca2 100644
--- a/cointop/update.go
+++ b/cointop/update.go
@@ -1,6 +1,6 @@
package cointop
-import "github.com/jroimartin/gocui"
+import "github.com/miguelmota/cointop/pkg/gocui"
// update update view
func (ct *Cointop) update(f func()) {
diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go
new file mode 100644
index 0000000..db88d2f
--- /dev/null
+++ b/pkg/cache/cache.go
@@ -0,0 +1,1161 @@
+package cache
+
+import (
+ "encoding/gob"
+ "fmt"
+ "io"
+ "os"
+ "runtime"
+ "sync"
+ "time"
+)
+
+type Item struct {
+ Object interface{}
+ Expiration int64
+}
+
+// Returns true if the item has expired.
+func (item Item) Expired() bool {
+ if item.Expiration == 0 {
+ return false
+ }
+ return time.Now().UnixNano() > item.Expiration
+}
+
+const (
+ // For use with functions that take an expiration time.
+ NoExpiration time.Duration = -1
+ // For use with functions that take an expiration time. Equivalent to
+ // passing in the same expiration duration as was given to New() or
+ // NewFrom() when the cache was created (e.g. 5 minutes.)
+ DefaultExpiration time.Duration = 0
+)
+
+type Cache struct {
+ *cache
+ // If this is confusing, see the comment at the bottom of New()
+}
+
+type cache struct {
+ defaultExpiration time.Duration
+ items map[string]Item
+ mu sync.RWMutex
+ onEvicted func(string, interface{})
+ janitor *janitor
+}
+
+// Add an item to the cache, replacing any existing item. If the duration is 0
+// (DefaultExpiration), the cache's default expiration time is used. If it is -1
+// (NoExpiration), the item never expires.
+func (c *cache) Set(k string, x interface{}, d time.Duration) {
+ // "Inlining" of set
+ var e int64
+ if d == DefaultExpiration {
+ d = c.defaultExpiration
+ }
+ if d > 0 {
+ e = time.Now().Add(d).UnixNano()
+ }
+ c.mu.Lock()
+ c.items[k] = Item{
+ Object: x,
+ Expiration: e,
+ }
+ // TODO: Calls to mu.Unlock are currently not deferred because defer
+ // adds ~200 ns (as of go1.)
+ c.mu.Unlock()
+}
+
+func (c *cache) set(k string, x interface{}, d time.Duration) {
+ var e int64
+ if d == DefaultExpiration {
+ d = c.defaultExpiration
+ }
+ if d > 0 {
+ e = time.Now().Add(d).UnixNano()
+ }
+ c.items[k] = Item{
+ Object: x,
+ Expiration: e,
+ }
+}
+
+// Add an item to the cache, replacing any existing item, using the default
+// expiration.
+func (c *cache) SetDefault(k string, x interface{}) {
+ c.Set(k, x, DefaultExpiration)
+}
+
+// Add an item to the cache only if an item doesn't already exist for the given
+// key, or if the existing item has expired. Returns an error otherwise.
+func (c *cache) Add(k string, x interface{}, d time.Duration) error {
+ c.mu.Lock()
+ _, found := c.get(k)
+ if found {
+ c.mu.Unlock()
+ return fmt.Errorf("Item %s already exists", k)
+ }
+ c.set(k, x, d)
+ c.mu.Unlock()
+ return nil
+}
+
+// Set a new value for the cache key only if it already exists, and the existing
+// item hasn't expired. Returns an error otherwise.
+func (c *cache) Replace(k string, x interface{}, d time.Duration) error {
+ c.mu.Lock()
+ _, found := c.get(k)
+ if !found {
+ c.mu.Unlock()
+ return fmt.Errorf("Item %s doesn't exist", k)
+ }
+ c.set(k, x, d)
+ c.mu.Unlock()
+ return nil
+}
+
+// Get an item from the cache. Returns the item or nil, and a bool indicating
+// whether the key was found.
+func (c *cache) Get(k string) (interface{}, bool) {
+ c.mu.RLock()
+ // "Inlining" of get and Expired
+ item, found := c.items[k]
+ if !found {
+ c.mu.RUnlock()
+ return nil, false
+ }
+ if item.Expiration > 0 {
+ if time.Now().UnixNano() > item.Expiration {
+ c.mu.RUnlock()
+ return nil, false
+ }
+ }
+ c.mu.RUnlock()
+ return item.Object, true
+}
+
+// GetWithExpiration returns an item and its expiration time from the cache.
+// It returns the item or nil, the expiration time if one is set (if the item
+// never expires a zero value for time.Time is returned), and a bool indicating
+// whether the key was found.
+func (c *cache) GetWithExpiration(k string) (interface{}, time.Time, bool) {
+ c.mu.RLock()
+ // "Inlining" of get and Expired
+ item, found := c.items[k]
+ if !found {
+ c.mu.RUnlock()
+ return nil, time.Time{}, false
+ }
+
+ if item.Expiration > 0 {
+ if time.Now().UnixNano() > item.Expiration {
+ c.mu.RUnlock()
+ return nil, time.Time{}, false
+ }
+
+ // Return the item and the expiration time
+ c.mu.RUnlock()
+ return item.Object, time.Unix(0, item.Expiration), true
+ }
+
+ // If expiration <= 0 (i.e. no expiration time set) then return the item
+ // and a zeroed time.Time
+ c.mu.RUnlock()
+ return item.Object, time.Time{}, true
+}
+
+func (c *cache) get(k string) (interface{}, bool) {
+ item, found := c.items[k]
+ if !found {
+ return nil, false
+ }
+ // "Inlining" of Expired
+ if item.Expiration > 0 {
+ if time.Now().UnixNano() > item.Expiration {
+ return nil, false
+ }
+ }
+ return item.Object, true
+}
+
+// Increment an item of type int, int8, int16, int32, int64, uintptr, uint,
+// uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the
+// item's value is not an integer, if it was not found, or if it is not
+// possible to increment it by n. To retrieve the incremented value, use one
+// of the specialized methods, e.g. IncrementInt64.
+func (c *cache) Increment(k string, n int64) error {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return fmt.Errorf("Item %s not found", k)
+ }
+ switch v.Object.(type) {
+ case int:
+ v.Object = v.Object.(int) + int(n)
+ case int8:
+ v.Object = v.Object.(int8) + int8(n)
+ case int16:
+ v.Object = v.Object.(int16) + int16(n)
+ case int32:
+ v.Object = v.Object.(int32) + int32(n)
+ case int64:
+ v.Object = v.Object.(int64) + n
+ case uint:
+ v.Object = v.Object.(uint) + uint(n)
+ case uintptr:
+ v.Object = v.Object.(uintptr) + uintptr(n)
+ case uint8:
+ v.Object = v.Object.(uint8) + uint8(n)
+ case uint16:
+ v.Object = v.Object.(uint16) + uint16(n)
+ case uint32:
+ v.Object = v.Object.(uint32) + uint32(n)
+ case uint64:
+ v.Object = v.Object.(uint64) + uint64(n)
+ case float32:
+ v.Object = v.Object.(float32) + float32(n)
+ case float64:
+ v.Object = v.Object.(float64) + float64(n)
+ default:
+ c.mu.Unlock()
+ return fmt.Errorf("The value for %s is not an integer", k)
+ }
+ c.items[k] = v
+ c.mu.Unlock()
+ return nil
+}
+
+// Increment an item of type float32 or float64 by n. Returns an error if the
+// item's value is not floating point, if it was not found, or if it is not
+// possible to increment it by n. Pass a negative number to decrement the
+// value. To retrieve the incremented value, use one of the specialized methods,
+// e.g. IncrementFloat64.
+func (c *cache) IncrementFloat(k string, n float64) error {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return fmt.Errorf("Item %s not found", k)
+ }
+ switch v.Object.(type) {
+ case float32:
+ v.Object = v.Object.(float32) + float32(n)
+ case float64:
+ v.Object = v.Object.(float64) + n
+ default:
+ c.mu.Unlock()
+ return fmt.Errorf("The value for %s does not have type float32 or float64", k)
+ }
+ c.items[k] = v
+ c.mu.Unlock()
+ return nil
+}
+
+// Increment an item of type int by n. Returns an error if the item's value is
+// not an int, or if it was not found. If there is no error, the incremented
+// value is returned.
+func (c *cache) IncrementInt(k string, n int) (int, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(int)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an int", k)
+ }
+ nv := rv + n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Increment an item of type int8 by n. Returns an error if the item's value is
+// not an int8, or if it was not found. If there is no error, the incremented
+// value is returned.
+func (c *cache) IncrementInt8(k string, n int8) (int8, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(int8)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an int8", k)
+ }
+ nv := rv + n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Increment an item of type int16 by n. Returns an error if the item's value is
+// not an int16, or if it was not found. If there is no error, the incremented
+// value is returned.
+func (c *cache) IncrementInt16(k string, n int16) (int16, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(int16)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an int16", k)
+ }
+ nv := rv + n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Increment an item of type int32 by n. Returns an error if the item's value is
+// not an int32, or if it was not found. If there is no error, the incremented
+// value is returned.
+func (c *cache) IncrementInt32(k string, n int32) (int32, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(int32)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an int32", k)
+ }
+ nv := rv + n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Increment an item of type int64 by n. Returns an error if the item's value is
+// not an int64, or if it was not found. If there is no error, the incremented
+// value is returned.
+func (c *cache) IncrementInt64(k string, n int64) (int64, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(int64)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an int64", k)
+ }
+ nv := rv + n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Increment an item of type uint by n. Returns an error if the item's value is
+// not an uint, or if it was not found. If there is no error, the incremented
+// value is returned.
+func (c *cache) IncrementUint(k string, n uint) (uint, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(uint)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an uint", k)
+ }
+ nv := rv + n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Increment an item of type uintptr by n. Returns an error if the item's value
+// is not an uintptr, or if it was not found. If there is no error, the
+// incremented value is returned.
+func (c *cache) IncrementUintptr(k string, n uintptr) (uintptr, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(uintptr)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an uintptr", k)
+ }
+ nv := rv + n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Increment an item of type uint8 by n. Returns an error if the item's value
+// is not an uint8, or if it was not found. If there is no error, the
+// incremented value is returned.
+func (c *cache) IncrementUint8(k string, n uint8) (uint8, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(uint8)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an uint8", k)
+ }
+ nv := rv + n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Increment an item of type uint16 by n. Returns an error if the item's value
+// is not an uint16, or if it was not found. If there is no error, the
+// incremented value is returned.
+func (c *cache) IncrementUint16(k string, n uint16) (uint16, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(uint16)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an uint16", k)
+ }
+ nv := rv + n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Increment an item of type uint32 by n. Returns an error if the item's value
+// is not an uint32, or if it was not found. If there is no error, the
+// incremented value is returned.
+func (c *cache) IncrementUint32(k string, n uint32) (uint32, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(uint32)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an uint32", k)
+ }
+ nv := rv + n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Increment an item of type uint64 by n. Returns an error if the item's value
+// is not an uint64, or if it was not found. If there is no error, the
+// incremented value is returned.
+func (c *cache) IncrementUint64(k string, n uint64) (uint64, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(uint64)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an uint64", k)
+ }
+ nv := rv + n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Increment an item of type float32 by n. Returns an error if the item's value
+// is not an float32, or if it was not found. If there is no error, the
+// incremented value is returned.
+func (c *cache) IncrementFloat32(k string, n float32) (float32, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(float32)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an float32", k)
+ }
+ nv := rv + n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Increment an item of type float64 by n. Returns an error if the item's value
+// is not an float64, or if it was not found. If there is no error, the
+// incremented value is returned.
+func (c *cache) IncrementFloat64(k string, n float64) (float64, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(float64)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an float64", k)
+ }
+ nv := rv + n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Decrement an item of type int, int8, int16, int32, int64, uintptr, uint,
+// uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the
+// item's value is not an integer, if it was not found, or if it is not
+// possible to decrement it by n. To retrieve the decremented value, use one
+// of the specialized methods, e.g. DecrementInt64.
+func (c *cache) Decrement(k string, n int64) error {
+ // TODO: Implement Increment and Decrement more cleanly.
+ // (Cannot do Increment(k, n*-1) for uints.)
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return fmt.Errorf("Item not found")
+ }
+ switch v.Object.(type) {
+ case int:
+ v.Object = v.Object.(int) - int(n)
+ case int8:
+ v.Object = v.Object.(int8) - int8(n)
+ case int16:
+ v.Object = v.Object.(int16) - int16(n)
+ case int32:
+ v.Object = v.Object.(int32) - int32(n)
+ case int64:
+ v.Object = v.Object.(int64) - n
+ case uint:
+ v.Object = v.Object.(uint) - uint(n)
+ case uintptr:
+ v.Object = v.Object.(uintptr) - uintptr(n)
+ case uint8:
+ v.Object = v.Object.(uint8) - uint8(n)
+ case uint16:
+ v.Object = v.Object.(uint16) - uint16(n)
+ case uint32:
+ v.Object = v.Object.(uint32) - uint32(n)
+ case uint64:
+ v.Object = v.Object.(uint64) - uint64(n)
+ case float32:
+ v.Object = v.Object.(float32) - float32(n)
+ case float64:
+ v.Object = v.Object.(float64) - float64(n)
+ default:
+ c.mu.Unlock()
+ return fmt.Errorf("The value for %s is not an integer", k)
+ }
+ c.items[k] = v
+ c.mu.Unlock()
+ return nil
+}
+
+// Decrement an item of type float32 or float64 by n. Returns an error if the
+// item's value is not floating point, if it was not found, or if it is not
+// possible to decrement it by n. Pass a negative number to decrement the
+// value. To retrieve the decremented value, use one of the specialized methods,
+// e.g. DecrementFloat64.
+func (c *cache) DecrementFloat(k string, n float64) error {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return fmt.Errorf("Item %s not found", k)
+ }
+ switch v.Object.(type) {
+ case float32:
+ v.Object = v.Object.(float32) - float32(n)
+ case float64:
+ v.Object = v.Object.(float64) - n
+ default:
+ c.mu.Unlock()
+ return fmt.Errorf("The value for %s does not have type float32 or float64", k)
+ }
+ c.items[k] = v
+ c.mu.Unlock()
+ return nil
+}
+
+// Decrement an item of type int by n. Returns an error if the item's value is
+// not an int, or if it was not found. If there is no error, the decremented
+// value is returned.
+func (c *cache) DecrementInt(k string, n int) (int, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(int)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an int", k)
+ }
+ nv := rv - n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Decrement an item of type int8 by n. Returns an error if the item's value is
+// not an int8, or if it was not found. If there is no error, the decremented
+// value is returned.
+func (c *cache) DecrementInt8(k string, n int8) (int8, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(int8)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an int8", k)
+ }
+ nv := rv - n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Decrement an item of type int16 by n. Returns an error if the item's value is
+// not an int16, or if it was not found. If there is no error, the decremented
+// value is returned.
+func (c *cache) DecrementInt16(k string, n int16) (int16, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(int16)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an int16", k)
+ }
+ nv := rv - n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Decrement an item of type int32 by n. Returns an error if the item's value is
+// not an int32, or if it was not found. If there is no error, the decremented
+// value is returned.
+func (c *cache) DecrementInt32(k string, n int32) (int32, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(int32)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an int32", k)
+ }
+ nv := rv - n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Decrement an item of type int64 by n. Returns an error if the item's value is
+// not an int64, or if it was not found. If there is no error, the decremented
+// value is returned.
+func (c *cache) DecrementInt64(k string, n int64) (int64, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(int64)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an int64", k)
+ }
+ nv := rv - n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Decrement an item of type uint by n. Returns an error if the item's value is
+// not an uint, or if it was not found. If there is no error, the decremented
+// value is returned.
+func (c *cache) DecrementUint(k string, n uint) (uint, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(uint)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an uint", k)
+ }
+ nv := rv - n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Decrement an item of type uintptr by n. Returns an error if the item's value
+// is not an uintptr, or if it was not found. If there is no error, the
+// decremented value is returned.
+func (c *cache) DecrementUintptr(k string, n uintptr) (uintptr, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(uintptr)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an uintptr", k)
+ }
+ nv := rv - n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Decrement an item of type uint8 by n. Returns an error if the item's value is
+// not an uint8, or if it was not found. If there is no error, the decremented
+// value is returned.
+func (c *cache) DecrementUint8(k string, n uint8) (uint8, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(uint8)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an uint8", k)
+ }
+ nv := rv - n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Decrement an item of type uint16 by n. Returns an error if the item's value
+// is not an uint16, or if it was not found. If there is no error, the
+// decremented value is returned.
+func (c *cache) DecrementUint16(k string, n uint16) (uint16, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(uint16)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an uint16", k)
+ }
+ nv := rv - n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Decrement an item of type uint32 by n. Returns an error if the item's value
+// is not an uint32, or if it was not found. If there is no error, the
+// decremented value is returned.
+func (c *cache) DecrementUint32(k string, n uint32) (uint32, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(uint32)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an uint32", k)
+ }
+ nv := rv - n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Decrement an item of type uint64 by n. Returns an error if the item's value
+// is not an uint64, or if it was not found. If there is no error, the
+// decremented value is returned.
+func (c *cache) DecrementUint64(k string, n uint64) (uint64, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(uint64)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an uint64", k)
+ }
+ nv := rv - n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Decrement an item of type float32 by n. Returns an error if the item's value
+// is not an float32, or if it was not found. If there is no error, the
+// decremented value is returned.
+func (c *cache) DecrementFloat32(k string, n float32) (float32, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(float32)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an float32", k)
+ }
+ nv := rv - n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Decrement an item of type float64 by n. Returns an error if the item's value
+// is not an float64, or if it was not found. If there is no error, the
+// decremented value is returned.
+func (c *cache) DecrementFloat64(k string, n float64) (float64, error) {
+ c.mu.Lock()
+ v, found := c.items[k]
+ if !found || v.Expired() {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("Item %s not found", k)
+ }
+ rv, ok := v.Object.(float64)
+ if !ok {
+ c.mu.Unlock()
+ return 0, fmt.Errorf("The value for %s is not an float64", k)
+ }
+ nv := rv - n
+ v.Object = nv
+ c.items[k] = v
+ c.mu.Unlock()
+ return nv, nil
+}
+
+// Delete an item from the cache. Does nothing if the key is not in the cache.
+func (c *cache) Delete(k string) {
+ c.mu.Lock()
+ v, evicted := c.delete(k)
+ c.mu.Unlock()
+ if evicted {
+ c.onEvicted(k, v)
+ }
+}
+
+func (c *cache) delete(k string) (interface{}, bool) {
+ if c.onEvicted != nil {
+ if v, found := c.items[k]; found {
+ delete(c.items, k)
+ return v.Object, true
+ }
+ }
+ delete(c.items, k)
+ return nil, false
+}
+
+type keyAndValue struct {
+ key string
+ value interface{}
+}
+
+// Delete all expired items from the cache.
+func (c *cache) DeleteExpired() {
+ var evictedItems []keyAndValue
+ now := time.Now().UnixNano()
+ c.mu.Lock()
+ for k, v := range c.items {
+ // "Inlining" of expired
+ if v.Expiration > 0 && now > v.Expiration {
+ ov, evicted := c.delete(k)
+ if evicted {
+ evictedItems = append(evictedItems, keyAndValue{k, ov})
+ }
+ }
+ }
+ c.mu.Unlock()
+ for _, v := range evictedItems {
+ c.onEvicted(v.key, v.value)
+ }
+}
+
+// Sets an (optional) function that is called with the key and value when an
+// item is evicted from the cache. (Including when it is deleted manually, but
+// not when it is overwritten.) Set to nil to disable.
+func (c *cache) OnEvicted(f func(string, interface{})) {
+ c.mu.Lock()
+ c.onEvicted = f
+ c.mu.Unlock()
+}
+
+// Write the cache's items (using Gob) to an io.Writer.
+//
+// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
+// documentation for NewFrom().)
+func (c *cache) Save(w io.Writer) (err error) {
+ enc := gob.NewEncoder(w)
+ defer func() {
+ if x := recover(); x != nil {
+ err = fmt.Errorf("Error registering item types with Gob library")
+ }
+ }()
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ for _, v := range c.items {
+ gob.Register(v.Object)
+ }
+ err = enc.Encode(&c.items)
+ return
+}
+
+// Save the cache's items to the given filename, creating the file if it
+// doesn't exist, and overwriting it if it does.
+//
+// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
+// documentation for NewFrom().)
+func (c *cache) SaveFile(fname string) error {
+ fp, err := os.Create(fname)
+ if err != nil {
+ return err
+ }
+ err = c.Save(fp)
+ if err != nil {
+ fp.Close()
+ return err
+ }
+ return fp.Close()
+}
+
+// Add (Gob-serialized) cache items from an io.Reader, excluding any items with
+// keys that already exist (and haven't expired) in the current cache.
+//
+// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
+// documentation for NewFrom().)
+func (c *cache) Load(r io.Reader) error {
+ dec := gob.NewDecoder(r)
+ items := map[string]Item{}
+ err := dec.Decode(&items)
+ if err == nil {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ for k, v := range items {
+ ov, found := c.items[k]
+ if !found || ov.Expired() {
+ c.items[k] = v
+ }
+ }
+ }
+ return err
+}
+
+// Load and add cache items from the given filename, excluding any items with
+// keys that already exist in the current cache.
+//
+// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the
+// documentation for NewFrom().)
+func (c *cache) LoadFile(fname string) error {
+ fp, err := os.Open(fname)
+ if err != nil {
+ return err
+ }
+ err = c.Load(fp)
+ if err != nil {
+ fp.Close()
+ return err
+ }
+ return fp.Close()
+}
+
+// Copies all unexpired items in the cache into a new map and returns it.
+func (c *cache) Items() map[string]Item {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+ m := make(map[string]Item, len(c.items))
+ now := time.Now().UnixNano()
+ for k, v := range c.items {
+ // "Inlining" of Expired
+ if v.Expiration > 0 {
+ if now > v.Expiration {
+ continue
+ }
+ }
+ m[k] = v
+ }
+ return m
+}
+
+// Returns the number of items in the cache. This may include items that have
+// expired, but have not yet been cleaned up.
+func (c *cache) ItemCount() int {
+ c.mu.RLock()
+ n := len(c.items)
+ c.mu.RUnlock()
+ return n
+}
+
+// Delete all items from the cache.
+func (c *cache) Flush() {
+ c.mu.Lock()
+ c.items = map[string]Item{}
+ c.mu.Unlock()
+}
+
+type janitor struct {
+ Interval time.Duration
+ stop chan bool
+}
+
+func (j *janitor) Run(c *cache) {
+ ticker := time.NewTicker(j.Interval)
+ for {
+ select {
+ case <-ticker.C:
+ c.DeleteExpired()
+ case <-j.stop:
+ ticker.Stop()
+ return
+ }
+ }
+}
+
+func stopJanitor(c *Cache) {
+ c.janitor.stop <- true
+}
+
+func runJanitor(c *cache, ci time.Duration) {
+ j := &janitor{
+ Interval: ci,
+ stop: make(chan bool),
+ }
+ c.janitor = j
+ go j.Run(c)
+}
+
+func newCache(de time.Duration, m map[string]Item) *cache {
+ if de == 0 {
+ de = -1
+ }
+ c := &cache{
+ defaultExpiration: de,
+ items: m,
+ }
+ return c
+}
+
+func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache {
+ c := newCache(de, m)
+ // This trick ensures that the janitor goroutine (which--granted it
+ // was enabled--is running DeleteExpired on c forever) does not keep
+ // the returned C object from being garbage collected. When it is
+ // garbage collected, the finalizer stops the janitor goroutine, after
+ // which c can be collected.
+ C := &Cache{c}
+ if ci > 0 {
+ runJanitor(c, ci)
+ runtime.SetFinalizer(C, stopJanitor)
+ }
+ return C
+}
+
+// Return a new cache with a given default expiration duration and cleanup
+// interval. If the expiration duration is less than one (or NoExpiration),
+// the items in the cache never expire (by default), and must be deleted
+// manually. If the cleanup interval is less than one, expired items are not
+// deleted from the cache before calling c.DeleteExpired().
+func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
+ items := make(map[string]Item)
+ return newCacheWithJanitor(defaultExpiration, cleanupInterval, items)
+}
+
+// Return a new cache with a given default expiration duration and cleanup
+// interval. If the expiration duration is less than one (or NoExpiration),
+// the items in the cache never expire (by default), and must be deleted
+// manually. If the cleanup interval is less than one, expired items are not
+// deleted from the cache before calling c.DeleteExpired().
+//
+// NewFrom() also accepts an items map which will serve as the underlying map
+// for the cache. This is useful for starting from a deserialized cache
+// (serialized using e.g. gob.Encode() on c.Items()), or passing in e.g.
+// make(map[string]Item, 500) to improve startup performance when the cache
+// is expected to reach a certain minimum size.
+//
+// Only the cache's methods synchronize access to this map, so it is not
+// recommended to keep any references to the map around after creating a cache.
+// If need be, the map can be accessed at a later point using c.Items() (subject
+// to the same caveat.)
+//
+// Note regarding serialization: When using e.g. gob, make sure to
+// gob.Register() the individual types stored in the cache before encoding a
+// map retrieved with c.Items(), and to register those same types before
+// decoding a blob containing an items map.
+func NewFrom(defaultExpiration, cleanupInterval time.Duration, items map[string]Item) *Cache {
+ return newCacheWithJanitor(defaultExpiration, cleanupInterval, items)
+}
diff --git a/pkg/gocui/attribute.go b/pkg/gocui/attribute.go
new file mode 100644
index 0000000..bad758a
--- /dev/null
+++ b/pkg/gocui/attribute.go
@@ -0,0 +1,32 @@
+// Copyright 2014 The gocui 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 gocui
+
+import "github.com/nsf/termbox-go"
+
+// Attribute represents a terminal attribute, like color, font style, etc. They
+// can be combined using bitwise OR (|). Note that it is not possible to
+// combine multiple color attributes.
+type Attribute termbox.Attribute
+
+// Color attributes.
+const (
+ ColorDefault Attribute = Attribute(termbox.ColorDefault)
+ ColorBlack = Attribute(termbox.ColorBlack)
+ ColorRed = Attribute(termbox.ColorRed)
+ ColorGreen = Attribute(termbox.ColorGreen)
+ ColorYellow = Attribute(termbox.ColorYellow)
+ ColorBlue = Attribute(termbox.ColorBlue)
+ ColorMagenta = Attribute(termbox.ColorMagenta)
+ ColorCyan = Attribute(termbox.ColorCyan)
+ ColorWhite = Attribute(termbox.ColorWhite)
+)
+
+// Text style attributes.
+const (
+ AttrBold Attribute = Attribute(termbox.AttrBold)
+ AttrUnderline = Attribute(termbox.AttrUnderline)
+ AttrReverse = Attribute(termbox.AttrReverse)
+)
diff --git a/pkg/gocui/edit.go b/pkg/gocui/edit.go
new file mode 100644
index 0000000..e1b19c2
--- /dev/null
+++ b/pkg/gocui/edit.go
@@ -0,0 +1,344 @@
+// Copyright 2014 The gocui 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 gocui
+
+import "errors"
+
+const maxInt = int(^uint(0) >> 1)
+
+// Editor interface must be satisfied by gocui editors.
+type Editor interface {
+ Edit(v *View, key Key, ch rune, mod Modifier)
+}
+
+// The EditorFunc type is an adapter to allow the use of ordinary functions as
+// Editors. If f is a function with the appropriate signature, EditorFunc(f)
+// is an Editor object that calls f.
+type EditorFunc func(v *View, key Key, ch rune, mod Modifier)
+
+// Edit calls f(v, key, ch, mod)
+func (f EditorFunc) Edit(v *View, key Key, ch rune, mod Modifier) {
+ f(v, key, ch, mod)
+}
+
+// DefaultEditor is the default editor.
+var DefaultEditor Editor = EditorFunc(simpleEditor)
+
+// simpleEditor is used as the default gocui editor.
+func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
+ switch {
+ case ch != 0 && mod == 0:
+ v.EditWrite(ch)
+ case key == KeySpace:
+ v.EditWrite(' ')
+ case key == KeyBackspace || key == KeyBackspace2:
+ v.EditDelete(true)
+ case key == KeyDelete:
+ v.EditDelete(false)
+ case key == KeyInsert:
+ v.Overwrite = !v.Overwrite
+ case key == KeyEnter:
+ v.EditNewLine()
+ case key == KeyArrowDown:
+ v.MoveCursor(0, 1, false)
+ case key == KeyArrowUp:
+ v.MoveCursor(0, -1, false)
+ case key == KeyArrowLeft:
+ v.MoveCursor(-1, 0, false)
+ case key == KeyArrowRight:
+ v.MoveCursor(1, 0, false)
+ }
+}
+
+// EditWrite writes a rune at the cursor position.
+func (v *View) EditWrite(ch rune) {
+ v.writeRune(v.cx, v.cy, ch)
+ v.MoveCursor(1, 0, true)
+}
+
+// EditDelete deletes a rune at the cursor position. back determines the
+// direction.
+func (v *View) EditDelete(back bool) {
+ x, y := v.ox+v.cx, v.oy+v.cy
+ if y < 0 {
+ return
+ } else if y >= len(v.viewLines) {
+ v.MoveCursor(-1, 0, true)
+ return
+ }
+
+ maxX, _ := v.Size()
+ if back {
+ if x == 0 { // start of the line
+ if y < 1 {
+ return
+ }
+
+ var maxPrevWidth int
+ if v.Wrap {
+ maxPrevWidth = maxX
+ } else {
+ maxPrevWidth = maxInt
+ }
+
+ if v.viewLines[y].linesX == 0 { // regular line
+ v.mergeLines(v.cy - 1)
+ if len(v.viewLines[y-1].line) < maxPrevWidth {
+ v.MoveCursor(-1, 0, true)
+ }
+ } else { // wrapped line
+ v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1)
+ v.MoveCursor(-1, 0, true)
+ }
+ } else { // middle/end of the line
+ v.deleteRune(v.cx-1, v.cy)
+ v.MoveCursor(-1, 0, true)
+ }
+ } else {
+ if x == len(v.viewLines[y].line) { // end of the line
+ v.mergeLines(v.cy)
+ } else { // start/middle of the line
+ v.deleteRune(v.cx, v.cy)
+ }
+ }
+}
+
+// EditNewLine inserts a new line under the cursor.
+func (v *View) EditNewLine() {
+ v.breakLine(v.cx, v.cy)
+ v.ox = 0
+ v.cx = 0
+ v.MoveCursor(0, 1, true)
+}
+
+// MoveCursor moves the cursor taking into account the width of the line/view,
+// displacing the origin if necessary.
+func (v *View) MoveCursor(dx, dy int, writeMode bool) {
+ maxX, maxY := v.Size()
+ cx, cy := v.cx+dx, v.cy+dy
+ x, y := v.ox+cx, v.oy+cy
+
+ var curLineWidth, prevLineWidth int
+ // get the width of the current line
+ if writeMode {
+ if v.Wrap {
+ curLineWidth = maxX - 1
+ } else {
+ curLineWidth = maxInt
+ }
+ } else {
+ if y >= 0 && y < len(v.viewLines) {
+ curLineWidth = len(v.viewLines[y].line)
+ if v.Wrap && curLineWidth >= maxX {
+ curLineWidth = maxX - 1
+ }
+ } else {
+ curLineWidth = 0
+ }
+ }
+ // get the width of the previous line
+ if y-1 >= 0 && y-1 < len(v.viewLines) {
+ prevLineWidth = len(v.viewLines[y-1].line)
+ } else {
+ prevLineWidth = 0
+ }
+
+ // adjust cursor's x position and view's x origin
+ if x > curLineWidth { // move to next line
+ if dx > 0 { // horizontal movement
+ cy++
+ if writeMode || v.oy+cy < len(v.viewLines) {
+ if !v.Wrap {
+ v.ox = 0
+ }
+ v.cx = 0
+ }
+ } else { // vertical movement
+ if curLineWidth > 0 { // move cursor to the EOL
+ if v.Wrap {
+ v.cx = curLineWidth
+ } else {
+ ncx := curLineWidth - v.ox
+ if ncx < 0 {
+ v.ox += ncx
+ if v.ox < 0 {
+ v.ox = 0
+ }
+ v.cx = 0
+ } else {
+ v.cx = ncx
+ }
+ }
+ } else {
+ if writeMode || v.oy+cy < len(v.viewLines) {
+ if !v.Wrap {
+ v.ox = 0
+ }
+ v.cx = 0
+ }
+ }
+ }
+ } else if cx < 0 {
+ if !v.Wrap && v.ox > 0 { // move origin to the left
+ v.ox += cx
+ v.cx = 0
+ } else { // move to previous line
+ cy--
+ if prevLineWidth > 0 {
+ if !v.Wrap { // set origin so the EOL is visible
+ nox := prevLineWidth - maxX + 1
+ if nox < 0 {
+ v.ox = 0
+ } else {
+ v.ox = nox
+ }
+ }
+ v.cx = prevLineWidth
+ } else {
+ if !v.Wrap {
+ v.ox = 0
+ }
+ v.cx = 0
+ }
+ }
+ } else { // stay on the same line
+ if v.Wrap {
+ v.cx = cx
+ } else {
+ if cx >= maxX {
+ v.ox += cx - maxX + 1
+ v.cx = maxX
+ } else {
+ v.cx = cx
+ }
+ }
+ }
+
+ // adjust cursor's y position and view's y origin
+ if cy < 0 {
+ if v.oy > 0 {
+ v.oy--
+ }
+ } else if writeMode || v.oy+cy < len(v.viewLines) {
+ if cy >= maxY {
+ v.oy++
+ } else {
+ v.cy = cy
+ }
+ }
+}
+
+// writeRune writes a rune into the view's internal buffer, at the
+// position corresponding to the point (x, y). The length of the internal
+// buffer is increased if the point is out of bounds. Overwrite mode is
+// governed by the value of View.overwrite.
+func (v *View) writeRune(x, y int, ch rune) error {
+ v.tainted = true
+
+ x, y, err := v.realPosition(x, y)
+ if err != nil {
+ return err
+ }
+
+ if x < 0 || y < 0 {
+ return errors.New("invalid point")
+ }
+
+ if y >= len(v.lines) {
+ s := make([][]cell, y-len(v.lines)+1)
+ v.lines = append(v.lines, s...)
+ }
+
+ olen := len(v.lines[y])
+
+ var s []cell
+ if x >= len(v.lines[y]) {
+ s = make([]cell, x-len(v.lines[y])+1)
+ } else if !v.Overwrite {
+ s = make([]cell, 1)
+ }
+ v.lines[y] = append(v.lines[y], s...)
+
+ if !v.Overwrite || (v.Overwrite && x >= olen-1) {
+ copy(v.lines[y][x+1:], v.lines[y][x:])
+ }
+ v.lines[y][x] = cell{
+ fgColor: v.FgColor,
+ bgColor: v.BgColor,
+ chr: ch,
+ }
+
+ return nil
+}
+
+// deleteRune removes a rune from the view's internal buffer, at the
+// position corresponding to the point (x, y).
+func (v *View) deleteRune(x, y int) error {
+ v.tainted = true
+
+ x, y, err := v.realPosition(x, y)
+ if err != nil {
+ return err
+ }
+
+ if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
+ return errors.New("invalid point")
+ }
+ v.lines[y] = append(v.lines[y][:x], v.lines[y][x+1:]...)
+ return nil
+}
+
+// mergeLines merges the lines "y" and "y+1" if possible.
+func (v *View) mergeLines(y int) error {
+ v.tainted = true
+
+ _, y, err := v.realPosition(0, y)
+ if err != nil {
+ return err
+ }
+
+ if y < 0 || y >= len(v.lines) {
+ return errors.New("invalid point")
+ }
+
+ if y < len(v.lines)-1 { // otherwise we don't need to merge anything
+ v.lines[y] = append(v.lines[y], v.lines[y+1]...)
+ v.lines = append(v.lines[:y+1], v.lines[y+2:]...)
+ }
+ return nil
+}
+
+// breakLine breaks a line of the internal buffer at the position corresponding
+// to the point (x, y).
+func (v *View) breakLine(x, y int) error {
+ v.tainted = true
+
+ x, y, err := v.realPosition(x, y)
+ if err != nil {
+ return err
+ }
+
+ if y < 0 || y >= len(v.lines) {
+ return errors.New("invalid point")
+ }
+
+ var left, right []cell
+ if x < len(v.lines[y]) { // break line
+ left = make([]cell, len(v.lines[y][:x]))
+ copy(left, v.lines[y][:x])
+ right = make([]cell, len(v.lines[y][x:]))
+ copy(right, v.lines[y][x:])
+ } else { // new empty line
+ left = v.lines[y]
+ }
+
+ lines := make([][]cell, len(v.lines)+1)
+ lines[y] = left
+ lines[y+1] = right
+ copy(lines, v.lines[:y])
+ copy(lines[y+2:], v.lines[y+1:])
+ v.lines = lines
+ return nil
+}
diff --git a/pkg/gocui/escape.go b/pkg/gocui/escape.go
new file mode 100644
index 0000000..ec31bbe
--- /dev/null
+++ b/pkg/gocui/escape.go
@@ -0,0 +1,229 @@
+// Copyright 2014 The gocui 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 gocui
+
+import (
+ "errors"
+ "strconv"
+)
+
+type escapeInterpreter struct {
+ state escapeState
+ curch rune
+ csiParam []string
+ curFgColor, curBgColor Attribute
+ mode OutputMode
+}
+
+type escapeState int
+
+const (
+ stateNone escapeState = iota
+ stateEscape
+ stateCSI
+ stateParams
+)
+
+var (
+ errNotCSI = errors.New("Not a CSI escape sequence")
+ errCSIParseError = errors.New("CSI escape sequence parsing error")
+ errCSITooLong = errors.New("CSI escape sequence is too long")
+)
+
+// runes in case of error will output the non-parsed runes as a string.
+func (ei *escapeInterpreter) runes() []rune {
+ switch ei.state {
+ case stateNone:
+ return []rune{0x1b}
+ case stateEscape:
+ return []rune{0x1b, ei.curch}
+ case stateCSI:
+ return []rune{0x1b, '[', ei.curch}
+ case stateParams:
+ ret := []rune{0x1b, '['}
+ for _, s := range ei.csiParam {
+ ret = append(ret, []rune(s)...)
+ ret = append(ret, ';')
+ }
+ return append(ret, ei.curch)
+ }
+ return nil
+}
+
+// newEscapeInterpreter returns an escapeInterpreter that will be able to parse
+// terminal escape sequences.
+func newEscapeInterpreter(mode OutputMode) *escapeInterpreter {
+ ei := &escapeInterpreter{
+ state: stateNone,
+ curFgColor: ColorDefault,
+ curBgColor: ColorDefault,
+ mode: mode,
+ }
+ return ei
+}
+
+// reset sets the escapeInterpreter in initial state.
+func (ei *escapeInterpreter) reset() {
+ ei.state = stateNone
+ ei.curFgColor = ColorDefault
+ ei.curBgColor = ColorDefault
+ ei.csiParam = nil
+}
+
+// parseOne parses a rune. If isEscape is true, it means that the rune is part
+// of an escape sequence, and as such should not be printed verbatim. Otherwise,
+// it's not an escape sequence.
+func (ei *escapeInterpreter) parseOne(ch rune) (isEscape bool, err error) {
+ // Sanity checks
+ if len(ei.csiParam) > 20 {
+ return false, errCSITooLong
+ }
+ if len(ei.csiParam) > 0 && len(ei.csiParam[len(ei.csiParam)-1]) > 255 {
+ return false, errCSITooLong
+ }
+
+ ei.curch = ch
+
+ switch ei.state {
+ case stateNone:
+ if ch == 0x1b {
+ ei.state = stateEscape
+ return true, nil
+ }
+ return false, nil
+ case stateEscape:
+ if ch == '[' {
+ ei.state = stateCSI
+ return true, nil
+ }
+ return false, errNotCSI
+ case stateCSI:
+ switch {
+ case ch >= '0' && ch <= '9':
+ ei.csiParam = append(ei.csiParam, "")
+ case ch == 'm':
+ ei.csiParam = append(ei.csiParam, "0")
+ default:
+ return false, errCSIParseError
+ }
+ ei.state = stateParams
+ fallthrough
+ case stateParams:
+ switch {
+ case ch >= '0' && ch <= '9':
+ ei.csiParam[len(ei.csiParam)-1] += string(ch)
+ return true, nil
+ case ch == ';':
+ ei.csiParam = append(ei.csiParam, "")
+ return true, nil
+ case ch == 'm':
+ var err error
+ switch ei.mode {
+ case OutputNormal:
+ err = ei.outputNormal()
+ case Output256:
+ err = ei.output256()
+ }
+ if err != nil {
+ return false, errCSIParseError
+ }
+
+ ei.state = stateNone
+ ei.csiParam = nil
+ return true, nil
+ default:
+ return false, errCSIParseError
+ }
+ }
+ return false, nil
+}
+
+// outputNormal provides 8 different colors:
+// black, red, green, yellow, blue, magenta, cyan, white
+func (ei *escapeInterpreter) outputNormal() error {
+ for _, param := range ei.csiParam {
+ p, err := strconv.Atoi(param)
+ if err != nil {
+ return errCSIParseError
+ }
+
+ switch {
+ case p >= 30 && p <= 37:
+ ei.curFgColor = Attribute(p - 30 + 1)
+ case p == 39:
+ ei.curFgColor = ColorDefault
+ case p >= 40 && p <= 47:
+ ei.curBgColor = Attribute(p - 40 + 1)
+ case p == 49:
+ ei.curBgColor = ColorDefault
+ case p == 1:
+ ei.curFgColor |= AttrBold
+ case p == 4:
+ ei.curFgColor |= AttrUnderline
+ case p == 7:
+ ei.curFgColor |= AttrReverse
+ case p == 0:
+ ei.curFgColor = ColorDefault
+ ei.curBgColor = ColorDefault
+ }
+ }
+
+ return nil
+}
+
+// output256 allows you to leverage the 256-colors terminal mode:
+// 0x01 - 0x08: the 8 colors as in OutputNormal
+// 0x09 - 0x10: Color* | AttrBold
+// 0x11 - 0xe8: 216 different colors
+// 0xe9 - 0x1ff: 24 different shades of grey
+func (ei *escapeInterpreter) output256() error {
+ if len(ei.csiParam) < 3 {
+ return ei.outputNormal()
+ }
+
+ mode, err := strconv.Atoi(ei.csiParam[1])
+ if err != nil {
+ return errCSIParseError
+ }
+ if mode != 5 {
+ return ei.outputNormal()
+ }
+
+ fgbg, err := strconv.Atoi(ei.csiParam[0])
+ if err != nil {
+ return errCSIParseError
+ }
+ color, err := strconv.Atoi(ei.csiParam[2])
+ if err != nil {
+ return errCSIParseError
+ }
+
+ switch fgbg {
+ case 38:
+ ei.curFgColor = Attribute(color + 1)
+
+ for _, param := range ei.csiParam[3:] {
+ p, err := strconv.Atoi(param)
+ if err != nil {
+ return errCSIParseError
+ }
+
+ switch {
+ case p == 1:
+ ei.curFgColor |= AttrBold
+ case p == 4:
+ ei.curFgColor |= AttrUnderline
+ case p == 7:
+ ei.curFgColor |= AttrReverse
+ }
+ }
+ case 48:
+ ei.curBgColor = Attribute(color + 1)
+ default:
+ return errCSIParseError
+ }
+
+ return nil
+}
diff --git a/pkg/gocui/gocui.go b/pkg/gocui/gocui.go
new file mode 100644
index 0000000..9499d3c
--- /dev/null
+++ b/pkg/gocui/gocui.go
@@ -0,0 +1,636 @@
+// Copyright 2014 The gocui 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 gocui
+
+import (
+ "errors"
+
+ "github.com/nsf/termbox-go"
+)
+
+var (
+ // ErrQuit is used to decide if the MainLoop finished successfully.
+ ErrQuit = errors.New("quit")
+
+ // ErrUnknownView allows to assert if a View must be initialized.
+ ErrUnknownView = errors.New("unknown view")
+)
+
+// OutputMode represents the terminal's output mode (8 or 256 colors).
+type OutputMode termbox.OutputMode
+
+const (
+ // OutputNormal provides 8-colors terminal mode.
+ OutputNormal = OutputMode(termbox.OutputNormal)
+
+ // Output256 provides 256-colors terminal mode.
+ Output256 = OutputMode(termbox.Output256)
+)
+
+// Gui represents the whole User Interface, including the views, layouts
+// and keybindings.
+type Gui struct {
+ tbEvents chan termbox.Event
+ userEvents chan userEvent
+ views []*View
+ currentView *View
+ managers []Manager
+ keybindings []*keybinding
+ maxX, maxY int
+ outputMode OutputMode
+
+ // BgColor and FgColor allow to configure the background and foreground
+ // colors of the GUI.
+ BgColor, FgColor Attribute
+
+ // SelBgColor and SelFgColor allow to configure the background and
+ // foreground colors of the frame of the current view.
+ SelBgColor, SelFgColor Attribute
+
+ // If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the
+ // frame of the current view.
+ Highlight bool
+
+ // If Cursor is true then the cursor is enabled.
+ Cursor bool
+
+ // If Mouse is true then mouse events will be enabled.
+ Mouse bool
+
+ // If InputEsc is true, when ESC sequence is in the buffer and it doesn't
+ // match any known sequence, ESC means KeyEsc.
+ InputEsc bool
+
+ // If ASCII is true then use ASCII instead of unicode to draw the
+ // interface. Using ASCII is more portable.
+ ASCII bool
+}
+
+// NewGui returns a new Gui object with a given output mode.
+func NewGui(mode OutputMode) (*Gui, error) {
+ if err := termbox.Init(); err != nil {
+ return nil, err
+ }
+
+ g := &Gui{}
+
+ g.outputMode = mode
+ termbox.SetOutputMode(termbox.OutputMode(mode))
+
+ g.tbEvents = make(chan termbox.Event, 20)
+ g.userEvents = make(chan userEvent, 20)
+
+ g.maxX, g.maxY = termbox.Size()
+
+ g.BgColor, g.FgColor = ColorDefault, ColorDefault
+ g.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault
+
+ return g, nil
+}
+
+// Close finalizes the library. It should be called after a successful
+// initialization and when gocui is not needed anymore.
+func (g *Gui) Close() {
+ termbox.Close()
+}
+
+// Size returns the terminal's size.
+func (g *Gui) Size() (x, y int) {
+ return g.maxX, g.maxY
+}
+
+// SetRune writes a rune at the given point, relative to the top-left
+// corner of the terminal. It checks if the position is valid and applies
+// the given colors.
+func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
+ if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
+ return errors.New("invalid point")
+ }
+ termbox.SetCell(x, y, ch, termbox.Attribute(fgColor), termbox.Attribute(bgColor))
+ return nil
+}
+
+// Rune returns the rune contained in the cell at the given position.
+// It checks if the position is valid.
+func (g *Gui) Rune(x, y int) (rune, error) {
+ if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
+ return ' ', errors.New("invalid point")
+ }
+ c := termbox.CellBuffer()[y*g.maxX+x]
+ return c.Ch, nil
+}
+
+// SetView creates a new view with its top-left corner at (x0, y0)
+// and the bottom-right one at (x1, y1). If a view with the same name
+// already exists, its dimensions are updated; otherwise, the error
+// ErrUnknownView is returned, which allows to assert if the View must
+// be initialized. It checks if the position is valid.
+func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
+ if x0 >= x1 || y0 >= y1 {
+ return nil, errors.New("invalid dimensions")
+ }
+ if name == "" {
+ return nil, errors.New("invalid name")
+ }
+
+ if v, err := g.View(name); err == nil {
+ v.x0 = x0
+ v.y0 = y0
+ v.x1 = x1
+ v.y1 = y1
+ v.tainted = true
+ return v, nil
+ }
+
+ v := newView(name, x0, y0, x1, y1, g.outputMode)
+ v.BgColor, v.FgColor = g.BgColor, g.FgColor
+ v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
+ g.views = append(g.views, v)
+ return v, ErrUnknownView
+}
+
+// SetViewOnTop sets the given view on top of the existing ones.
+func (g *Gui) SetViewOnTop(name string) (*View, error) {
+ for i, v := range g.views {
+ if v.name == name {
+ s := append(g.views[:i], g.views[i+1:]...)
+ g.views = append(s, v)
+ return v, nil
+ }
+ }
+ return nil, ErrUnknownView
+}
+
+// SetViewOnBottom sets the given view on bottom of the existing ones.
+func (g *Gui) SetViewOnBottom(name string) (*View, error) {
+ for i, v := range g.views {
+ if v.name == name {
+ s := append(g.views[:i], g.views[i+1:]...)
+ g.views = append([]*View{v}, s...)
+ return v, nil
+ }
+ }
+ return nil, ErrUnknownView
+}
+
+// Views returns all the views in the GUI.
+func (g *Gui) Views() []*View {
+ return g.views
+}
+
+// View returns a pointer to the view with the given name, or error
+// ErrUnknownView if a view with that name does not exist.
+func (g *Gui) View(name string) (*View, error) {
+ for _, v := range g.views {
+ if v.name == name {
+ return v, nil
+ }
+ }
+ return nil, ErrUnknownView
+}
+
+// ViewByPosition returns a pointer to a view matching the given position, or
+// error ErrUnknownView if a view in that position does not exist.
+func (g *Gui) ViewByPosition(x, y int) (*View, error) {
+ // traverse views in reverse order checking top views first
+ for i := len(g.views); i > 0; i-- {
+ v := g.views[i-1]
+ if x > v.x0 && x < v.x1 && y > v.y0 && y < v.y1 {
+ return v, nil
+ }
+ }
+ return nil, ErrUnknownView
+}
+
+// ViewPosition returns the coordinates of the view with the given name, or
+// error ErrUnknownView if a view with that name does not exist.
+func (g *Gui) ViewPosition(name string) (x0, y0, x1, y1 int, err error) {
+ for _, v := range g.views {
+ if v.name == name {
+ return v.x0, v.y0, v.x1, v.y1, nil
+ }
+ }
+ return 0, 0, 0, 0, ErrUnknownView
+}
+
+// DeleteView deletes a view by name.
+func (g *Gui) DeleteView(name string) error {
+ for i, v := range g.views {
+ if v.name == name {
+ g.views = append(g.views[:i], g.views[i+1:]...)
+ return nil
+ }
+ }
+ return ErrUnknownView
+}
+
+// SetCurrentView gives the focus to a given view.
+func (g *Gui) SetCurrentView(name string) (*View, error) {
+ for _, v := range g.views {
+ if v.name == name {
+ g.currentView = v
+ return v, nil
+ }
+ }
+ return nil, ErrUnknownView
+}
+
+// CurrentView returns the currently focused view, or nil if no view
+// owns the focus.
+func (g *Gui) CurrentView() *View {
+ return g.currentView
+}
+
+// SetKeybinding creates a new keybinding. If viewname equals to ""
+// (empty string) then the keybinding will apply to all views. key must
+// be a rune or a Key.
+func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error {
+ var kb *keybinding
+
+ k, ch, err := getKey(key)
+ if err != nil {
+ return err
+ }
+ kb = newKeybinding(viewname, k, ch, mod, handler)
+ g.keybindings = append(g.keybindings, kb)
+ return nil
+}
+
+// DeleteKeybinding deletes a keybinding.
+func (g *Gui) DeleteKeybinding(viewname string, key interface{}, mod Modifier) error {
+ k, ch, err := getKey(key)
+ if err != nil {
+ return err
+ }
+
+ for i, kb := range g.keybindings {
+ if kb.viewName == viewname && kb.ch == ch && kb.key == k && kb.mod == mod {
+ g.keybindings = append(g.keybindings[:i], g.keybindings[i+1:]...)
+ return nil
+ }
+ }
+ return errors.New("keybinding not found")
+}
+
+// DeleteKeybindings deletes all keybindings of view.
+func (g *Gui) DeleteKeybindings(viewname string) {
+ var s []*keybinding
+ for _, kb := range g.keybindings {
+ if kb.viewName != viewname {
+ s = append(s, kb)
+ }
+ }
+ g.keybindings = s
+}
+
+// getKey takes an empty interface with a key and returns the corresponding
+// typed Key or rune.
+func getKey(key interface{}) (Key, rune, error) {
+ switch t := key.(type) {
+ case Key:
+ return t, 0, nil
+ case rune:
+ return 0, t, nil
+ default:
+ return 0, 0, errors.New("unknown type")
+ }
+}
+
+// userEvent represents an event triggered by the user.
+type userEvent struct {
+ f func(*Gui) error
+}
+
+// Update executes the passed function. This method can be called safely from a
+// goroutine in order to update the GUI. It is important to note that the
+// passed function won't be executed immediately, instead it will be added to
+// the user events queue. Given that Update spawns a goroutine, the order in
+// which the user events will be handled is not guaranteed.
+func (g *Gui) Update(f func(*Gui) error) {
+ go func() { g.userEvents <- userEvent{f: f} }()
+}
+
+// A Manager is in charge of GUI's layout and can be used to build widgets.
+type Manager interface {
+ // Layout is called every time the GUI is redrawn, it must contain the
+ // base views and its initializations.
+ Layout(*Gui) error
+}
+
+// The ManagerFunc type is an adapter to allow the use of ordinary functions as
+// Managers. If f is a function with the appropriate signature, ManagerFunc(f)
+// is an Manager object that calls f.
+type ManagerFunc func(*Gui) error
+
+// Layout calls f(g)
+func (f ManagerFunc) Layout(g *Gui) error {
+ return f(g)
+}
+
+// SetManager sets the given GUI managers. It deletes all views and
+// keybindings.
+func (g *Gui) SetManager(managers ...Manager) {
+ g.managers = managers
+ g.currentView = nil
+ g.views = nil
+ g.keybindings = nil
+
+ go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }()
+}
+
+// SetManagerFunc sets the given manager function. It deletes all views and
+// keybindings.
+func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
+ g.SetManager(ManagerFunc(manager))
+}
+
+// MainLoop runs the main loop until an error is returned. A successful
+// finish should return ErrQuit.
+func (g *Gui) MainLoop() error {
+ go func() {
+ for {
+ g.tbEvents <- termbox.PollEvent()
+ }
+ }()
+
+ inputMode := termbox.InputAlt
+ if g.InputEsc {
+ inputMode = termbox.InputEsc
+ }
+ if g.Mouse {
+ inputMode |= termbox.InputMouse
+ }
+ termbox.SetInputMode(inputMode)
+
+ if err := g.flush(); err != nil {
+ return err
+ }
+ for {
+ select {
+ case ev := <-g.tbEvents:
+ if err := g.handleEvent(&ev); err != nil {
+ return err
+ }
+ case ev := <-g.userEvents:
+ if err := ev.f(g); err != nil {
+ return err
+ }
+ }
+ if err := g.consumeevents(); err != nil {
+ return err
+ }
+ if err := g.flush(); err != nil {
+ return err
+ }
+ }
+}
+
+// consumeevents handles the remaining events in the events pool.
+func (g *Gui) consumeevents() error {
+ for {
+ select {
+ case ev := <-g.tbEvents:
+ if err := g.handleEvent(&ev); err != nil {
+ return err
+ }
+ case ev := <-g.userEvents:
+ if err := ev.f(g); err != nil {
+ return err
+ }
+ default:
+ return nil
+ }
+ }
+}
+
+// handleEvent handles an event, based on its type (key-press, error,
+// etc.)
+func (g *Gui) handleEvent(ev *termbox.Event) error {
+ switch ev.Type {
+ case termbox.EventKey, termbox.EventMouse:
+ return g.onKey(ev)
+ case termbox.EventError:
+ return ev.Err
+ default:
+ return nil
+ }
+}
+
+// flush updates the gui, re-drawing frames and buffers.
+func (g *Gui) flush() error {
+ termbox.Clear(termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
+
+ maxX, maxY := termbox.Size()
+ // if GUI's size has changed, we need to redraw all views
+ if maxX != g.maxX || maxY != g.maxY {
+ for _, v := range g.views {
+ v.tainted = true
+ }
+ }
+ g.maxX, g.maxY = maxX, maxY
+
+ for _, m := range g.managers {
+ if err := m.Layout(g); err != nil {
+ return err
+ }
+ }
+ for _, v := range g.views {
+ if v.Frame {
+ var fgColor, bgColor Attribute
+ if g.Highlight && v == g.currentView {
+ fgColor = g.SelFgColor
+ bgColor = g.SelBgColor
+ } else {
+ fgColor = g.FgColor
+ bgColor = g.BgColor
+ }
+
+ if err := g.drawFrameEdges(v, fgColor, bgColor); err != nil {
+ return err
+ }
+ if err := g.drawFrameCorners(v, fgColor, bgColor); err != nil {
+ return err
+ }
+ if v.Title != "" {
+ if err := g.drawTitle(v, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ }
+ if err := g.draw(v); err != nil {
+ return err
+ }
+ }
+ termbox.Flush()
+ return nil
+}
+
+// drawFrameEdges draws the horizontal and vertical edges of a view.
+func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
+ runeH, runeV := '─', '│'
+ if g.ASCII {
+ runeH, runeV = '-', '|'
+ }
+
+ for x := v.x0 + 1; x < v.x1 && x < g.maxX; x++ {
+ if x < 0 {
+ continue
+ }
+ if v.y0 > -1 && v.y0 < g.maxY {
+ if err := g.SetRune(x, v.y0, runeH, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ if v.y1 > -1 && v.y1 < g.maxY {
+ if err := g.SetRune(x, v.y1, runeH, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ }
+ for y := v.y0 + 1; y < v.y1 && y < g.maxY; y++ {
+ if y < 0 {
+ continue
+ }
+ if v.x0 > -1 && v.x0 < g.maxX {
+ if err := g.SetRune(v.x0, y, runeV, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ if v.x1 > -1 && v.x1 < g.maxX {
+ if err := g.SetRune(v.x1, y, runeV, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// drawFrameCorners draws the corners of the view.
+func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
+ runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘'
+ if g.ASCII {
+ runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+'
+ }
+
+ corners := []struct {
+ x, y int
+ ch rune
+ }{{v.x0, v.y0, runeTL}, {v.x1, v.y0, runeTR}, {v.x0, v.y1, runeBL}, {v.x1, v.y1, runeBR}}
+
+ for _, c := range corners {
+ if c.x >= 0 && c.y >= 0 && c.x < g.maxX && c.y < g.maxY {
+ if err := g.SetRune(c.x, c.y, c.ch, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// drawTitle draws the title of the view.
+func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
+ if v.y0 < 0 || v.y0 >= g.maxY {
+ return nil
+ }
+
+ for i, ch := range v.Title {
+ x := v.x0 + i + 2
+ if x < 0 {
+ continue
+ } else if x > v.x1-2 || x >= g.maxX {
+ break
+ }
+ if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// draw manages the cursor and calls the draw function of a view.
+func (g *Gui) draw(v *View) error {
+ if g.Cursor {
+ if curview := g.currentView; curview != nil {
+ vMaxX, vMaxY := curview.Size()
+ if curview.cx < 0 {
+ curview.cx = 0
+ } else if curview.cx >= vMaxX {
+ curview.cx = vMaxX - 1
+ }
+ if curview.cy < 0 {
+ curview.cy = 0
+ } else if curview.cy >= vMaxY {
+ curview.cy = vMaxY - 1
+ }
+
+ gMaxX, gMaxY := g.Size()
+ cx, cy := curview.x0+curview.cx+1, curview.y0+curview.cy+1
+ if cx >= 0 && cx < gMaxX && cy >= 0 && cy < gMaxY {
+ termbox.SetCursor(cx, cy)
+ } else {
+ termbox.HideCursor()
+ }
+ }
+ } else {
+ termbox.HideCursor()
+ }
+
+ v.clearRunes()
+ if err := v.draw(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// onKey manages key-press events. A keybinding handler is called when
+// a key-press or mouse event satisfies a configured keybinding. Furthermore,
+// currentView's internal buffer is modified if currentView.Editable is true.
+func (g *Gui) onKey(ev *termbox.Event) error {
+ switch ev.Type {
+ case termbox.EventKey:
+ matched, err := g.execKeybindings(g.currentView, ev)
+ if err != nil {
+ return err
+ }
+ if matched {
+ break
+ }
+ if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil {
+ g.currentView.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
+ }
+ case termbox.EventMouse:
+ mx, my := ev.MouseX, ev.MouseY
+ v, err := g.ViewByPosition(mx, my)
+ if err != nil {
+ break
+ }
+ if err := v.SetCursor(mx-v.x0-1, my-v.y0-1); err != nil {
+ return err
+ }
+ if _, err := g.execKeybindings(v, ev); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// execKeybindings executes the keybinding handlers that match the passed view
+// and event. The value of matched is true if there is a match and no errors.
+func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {
+ matched = false
+ for _, kb := range g.keybindings {
+ if kb.handler == nil {
+ continue
+ }
+ if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(v) {
+ if err := kb.handler(g, v); err != nil {
+ return false, err
+ }
+ matched = true
+ }
+ }
+ return matched, nil
+}
diff --git a/pkg/gocui/keybinding.go b/pkg/gocui/keybinding.go
new file mode 100644
index 0000000..03fe677
--- /dev/null
+++ b/pkg/gocui/keybinding.go
@@ -0,0 +1,137 @@
+// Copyright 2014 The gocui 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 gocui
+
+import "github.com/nsf/termbox-go"
+
+// Keybidings are used to link a given key-press event with a handler.
+type keybinding struct {
+ viewName string
+ key Key
+ ch rune
+ mod Modifier
+ handler func(*Gui, *View) error
+}
+
+// newKeybinding returns a new Keybinding object.
+func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
+ kb = &keybinding{
+ viewName: viewname,
+ key: key,
+ ch: ch,
+ mod: mod,
+ handler: handler,
+ }
+ return kb
+}
+
+// matchKeypress returns if the keybinding matches the keypress.
+func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
+ return kb.key == key && kb.ch == ch && kb.mod == mod
+}
+
+// matchView returns if the keybinding matches the current view.
+func (kb *keybinding) matchView(v *View) bool {
+ if kb.viewName == "" {
+ return true
+ }
+ return v != nil && kb.viewName == v.name
+}
+
+// Key represents special keys or keys combinations.
+type Key termbox.Key
+
+// Special keys.
+const (
+ KeyF1 Key = Key(termbox.KeyF1)
+ KeyF2 = Key(termbox.KeyF2)
+ KeyF3 = Key(termbox.KeyF3)
+ KeyF4 = Key(termbox.KeyF4)
+ KeyF5 = Key(termbox.KeyF5)
+ KeyF6 = Key(termbox.KeyF6)
+ KeyF7 = Key(termbox.KeyF7)
+ KeyF8 = Key(termbox.KeyF8)
+ KeyF9 = Key(termbox.KeyF9)
+ KeyF10 = Key(termbox.KeyF10)
+ KeyF11 = Key(termbox.KeyF11)
+ KeyF12 = Key(termbox.KeyF12)
+ KeyInsert = Key(termbox.KeyInsert)
+ KeyDelete = Key(termbox.KeyDelete)
+ KeyHome = Key(termbox.KeyHome)
+ KeyEnd = Key(termbox.KeyEnd)
+ KeyPgup = Key(termbox.KeyPgup)
+ KeyPgdn = Key(termbox.KeyPgdn)
+ KeyArrowUp = Key(termbox.KeyArrowUp)
+ KeyArrowDown = Key(termbox.KeyArrowDown)
+ KeyArrowLeft = Key(termbox.KeyArrowLeft)
+ KeyArrowRight = Key(termbox.KeyArrowRight)
+
+ MouseLeft = Key(termbox.MouseLeft)
+ MouseMiddle = Key(termbox.MouseMiddle)
+ MouseRight = Key(termbox.MouseRight)
+ MouseRelease = Key(termbox.MouseRelease)
+ MouseWheelUp = Key(termbox.MouseWheelUp)
+ MouseWheelDown = Key(termbox.MouseWheelDown)
+)
+
+// Keys combinations.
+const (
+ KeyCtrlTilde Key = Key(termbox.KeyCtrlTilde)
+ KeyCtrl2 = Key(termbox.KeyCtrl2)
+ KeyCtrlSpace = Key(termbox.KeyCtrlSpace)
+ KeyCtrlA = Key(termbox.KeyCtrlA)
+ KeyCtrlB = Key(termbox.KeyCtrlB)
+ KeyCtrlC = Key(termbox.KeyCtrlC)
+ KeyCtrlD = Key(termbox.KeyCtrlD)
+ KeyCtrlE = Key(termbox.KeyCtrlE)
+ KeyCtrlF = Key(termbox.KeyCtrlF)
+ KeyCtrlG = Key(termbox.KeyCtrlG)
+ KeyBackspace = Key(termbox.KeyBackspace)
+ KeyCtrlH = Key(termbox.KeyCtrlH)
+ KeyTab = Key(termbox.KeyTab)
+ KeyCtrlI = Key(termbox.KeyCtrlI)
+ KeyCtrlJ = Key(termbox.KeyCtrlJ)
+ KeyCtrlK = Key(termbox.KeyCtrlK)
+ KeyCtrlL = Key(termbox.KeyCtrlL)
+ KeyEnter = Key(termbox.KeyEnter)
+ KeyCtrlM = Key(termbox.KeyCtrlM)
+ KeyCtrlN = Key(termbox.KeyCtrlN)
+ KeyCtrlO = Key(termbox.KeyCtrlO)
+ KeyCtrlP = Key(termbox.KeyCtrlP)
+ KeyCtrlQ = Key(termbox.KeyCtrlQ)
+ KeyCtrlR = Key(termbox.KeyCtrlR)
+ KeyCtrlS = Key(termbox.KeyCtrlS)
+ KeyCtrlT = Key(termbox.KeyCtrlT)
+ KeyCtrlU = Key(termbox.KeyCtrlU)
+ KeyCtrlV = Key(termbox.KeyCtrlV)
+ KeyCtrlW = Key(termbox.KeyCtrlW)
+ KeyCtrlX = Key(termbox.KeyCtrlX)
+ KeyCtrlY = Key(termbox.KeyCtrlY)
+ KeyCtrlZ = Key(termbox.KeyCtrlZ)
+ KeyEsc = Key(termbox.KeyEsc)
+ KeyCtrlLsqBracket = Key(termbox.KeyCtrlLsqBracket)
+ KeyCtrl3 = Key(termbox.KeyCtrl3)
+ KeyCtrl4 = Key(termbox.KeyCtrl4)
+ KeyCtrlBackslash = Key(termbox.KeyCtrlBackslash)
+ KeyCtrl5 = Key(termbox.KeyCtrl5)
+ KeyCtrlRsqBracket = Key(termbox.KeyCtrlRsqBracket)
+ KeyCtrl6 = Key(termbox.KeyCtrl6)
+ KeyCtrl7 = Key(termbox.KeyCtrl7)
+ KeyCtrlSlash = Key(termbox.KeyCtrlSlash)
+ KeyCtrlUnderscore = Key(termbox.KeyCtrlUnderscore)
+ KeySpace = Key(termbox.KeySpace)
+ KeyBackspace2 = Key(termbox.KeyBackspace2)
+ KeyCtrl8 = Key(termbox.KeyCtrl8)
+)
+
+// Modifier allows to define special keys combinations. They can be used
+// in combination with Keys or Runes when a new keybinding is defined.
+type Modifier termbox.Modifier
+
+// Modifiers.
+const (
+ ModNone Modifier = Modifier(0)
+ ModAlt = Modifier(termbox.ModAlt)
+)
diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go
new file mode 100644
index 0000000..42082f8
--- /dev/null
+++ b/pkg/gocui/view.go
@@ -0,0 +1,503 @@
+// Copyright 2014 The gocui 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 gocui
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "strings"
+
+ "github.com/nsf/termbox-go"
+)
+
+// A View is a window. It maintains its own internal buffer and cursor
+// position.
+type View struct {
+ name string
+ x0, y0, x1, y1 int
+ ox, oy int
+ cx, cy int
+ lines [][]cell
+ readOffset int
+ readCache string
+
+ tainted bool // marks if the viewBuffer must be updated
+ viewLines []viewLine // internal representation of the view's buffer
+
+ ei *escapeInterpreter // used to decode ESC sequences on Write
+
+ // BgColor and FgColor allow to configure the background and foreground
+ // colors of the View.
+ BgColor, FgColor Attribute
+
+ // SelBgColor and SelFgColor are used to configure the background and
+ // foreground colors of the selected line, when it is highlighted.
+ SelBgColor, SelFgColor Attribute
+
+ // If Editable is true, keystrokes will be added to the view's internal
+ // buffer at the cursor position.
+ Editable bool
+
+ // Editor allows to define the editor that manages the edition mode,
+ // including keybindings or cursor behaviour. DefaultEditor is used by
+ // default.
+ Editor Editor
+
+ // Overwrite enables or disables the overwrite mode of the view.
+ Overwrite bool
+
+ // If Highlight is true, Sel{Bg,Fg}Colors will be used
+ // for the line under the cursor position.
+ Highlight bool
+
+ // If Frame is true, a border will be drawn around the view.
+ Frame bool
+
+ // If Wrap is true, the content that is written to this View is
+ // automatically wrapped when it is longer than its width. If true the
+ // view's x-origin will be ignored.
+ Wrap bool
+
+ // If Autoscroll is true, the View will automatically scroll down when the
+ // text overflows. If true the view's y-origin will be ignored.
+ Autoscroll bool
+
+ // If Frame is true, Title allows to configure a title for the view.
+ Title string
+
+ // If Mask is true, the View will display the mask instead of the real
+ // content
+ Mask rune
+}
+
+type viewLine struct {
+ linesX, linesY int // coordinates relative to v.lines
+ line []cell
+}
+
+type cell struct {
+ chr rune
+ bgColor, fgColor Attribute
+}
+
+type lineType []cell
+
+// String returns a string from a given cell slice.
+func (l lineType) String() string {
+ str := ""
+ for _, c := range l {
+ str += string(c.chr)
+ }
+ return str
+}
+
+// newView returns a new View object.
+func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
+ v := &View{
+ name: name,
+ x0: x0,
+ y0: y0,
+ x1: x1,
+ y1: y1,
+ Frame: true,
+ Editor: DefaultEditor,
+ tainted: true,
+ ei: newEscapeInterpreter(mode),
+ }
+ return v
+}
+
+// Size returns the number of visible columns and rows in the View.
+func (v *View) Size() (x, y int) {
+ return v.x1 - v.x0 - 1, v.y1 - v.y0 - 1
+}
+
+// Name returns the name of the view.
+func (v *View) Name() string {
+ return v.name
+}
+
+// setRune sets a rune at the given point relative to the view. It applies the
+// specified colors, taking into account if the cell must be highlighted. Also,
+// it checks if the position is valid.
+func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
+ maxX, maxY := v.Size()
+ if x < 0 || x >= maxX || y < 0 || y >= maxY {
+ return errors.New("invalid point")
+ }
+
+ var (
+ ry, rcy int
+ err error
+ )
+ if v.Highlight {
+ _, ry, err = v.realPosition(x, y)
+ if err != nil {
+ return err
+ }
+ _, rcy, err = v.realPosition(v.cx, v.cy)
+ if err != nil {
+ return err
+ }
+ }
+
+ if v.Mask != 0 {
+ fgColor = v.FgColor
+ bgColor = v.BgColor
+ ch = v.Mask
+ } else if v.Highlight && ry == rcy {
+ fgColor = v.SelFgColor
+ bgColor = v.SelBgColor
+ }
+
+ termbox.SetCell(v.x0+x+1, v.y0+y+1, ch,
+ termbox.Attribute(fgColor), termbox.Attribute(bgColor))
+
+ return nil
+}
+
+// SetCursor sets the cursor position of the view at the given point,
+// relative to the view. It checks if the position is valid.
+func (v *View) SetCursor(x, y int) error {
+ maxX, maxY := v.Size()
+ if x < 0 || x >= maxX || y < 0 || y >= maxY {
+ return errors.New("invalid point")
+ }
+ v.cx = x
+ v.cy = y
+ return nil
+}
+
+// Cursor returns the cursor position of the view.
+func (v *View) Cursor() (x, y int) {
+ return v.cx, v.cy
+}
+
+// SetOrigin sets the origin position of the view's internal buffer,
+// so the buffer starts to be printed from this point, which means that
+// it is linked with the origin point of view. It can be used to
+// implement Horizontal and Vertical scrolling with just incrementing
+// or decrementing ox and oy.
+func (v *View) SetOrigin(x, y int) error {
+ if x < 0 || y < 0 {
+ return errors.New("invalid point")
+ }
+ v.ox = x
+ v.oy = y
+ return nil
+}
+
+// Origin returns the origin position of the view.
+func (v *View) Origin() (x, y int) {
+ return v.ox, v.oy
+}
+
+// Write appends a byte slice into the view's internal buffer. Because
+// View implements the io.Writer interface, it can be passed as parameter
+// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
+// be called to clear the view's buffer.
+func (v *View) Write(p []byte) (n int, err error) {
+ v.tainted = true
+
+ for _, ch := range bytes.Runes(p) {
+ switch ch {
+ case '\n':
+ v.lines = append(v.lines, nil)
+ case '\r':
+ nl := len(v.lines)
+ if nl > 0 {
+ v.lines[nl-1] = nil
+ } else {
+ v.lines = make([][]cell, 1)
+ }
+ default:
+ cells := v.parseInput(ch)
+ if cells == nil {
+ continue
+ }
+
+ nl := len(v.lines)
+ if nl > 0 {
+ v.lines[nl-1] = append(v.lines[nl-1], cells...)
+ } else {
+ v.lines = append(v.lines, cells)
+ }
+ }
+ }
+ return len(p), nil
+}
+
+// parseInput parses char by char the input written to the View. It returns nil
+// while processing ESC sequences. Otherwise, it returns a cell slice that
+// contains the processed data.
+func (v *View) parseInput(ch rune) []cell {
+ cells := []cell{}
+
+ isEscape, err := v.ei.parseOne(ch)
+ if err != nil {
+ for _, r := range v.ei.runes() {
+ c := cell{
+ fgColor: v.FgColor,
+ bgColor: v.BgColor,
+ chr: r,
+ }
+ cells = append(cells, c)
+ }
+ v.ei.reset()
+ } else {
+ if isEscape {
+ return nil
+ }
+ c := cell{
+ fgColor: v.ei.curFgColor,
+ bgColor: v.ei.curBgColor,
+ chr: ch,
+ }
+ cells = append(cells, c)
+ }
+
+ return cells
+}
+
+// Read reads data into p. It returns the number of bytes read into p.
+// At EOF, err will be io.EOF. Calling Read() after Rewind() makes the
+// cache to be refreshed with the contents of the view.
+func (v *View) Read(p []byte) (n int, err error) {
+ if v.readOffset == 0 {
+ v.readCache = v.Buffer()
+ }
+ if v.readOffset < len(v.readCache) {
+ n = copy(p, v.readCache[v.readOffset:])
+ v.readOffset += n
+ } else {
+ err = io.EOF
+ }
+ return
+}
+
+// Rewind sets the offset for the next Read to 0, which also refresh the
+// read cache.
+func (v *View) Rewind() {
+ v.readOffset = 0
+}
+
+// draw re-draws the view's contents.
+func (v *View) draw() error {
+ maxX, maxY := v.Size()
+
+ if v.Wrap {
+ if maxX == 0 {
+ return errors.New("X size of the view cannot be 0")
+ }
+ v.ox = 0
+ }
+ if v.tainted {
+ v.viewLines = nil
+ for i, line := range v.lines {
+ if v.Wrap {
+ if len(line) < maxX {
+ vline := viewLine{linesX: 0, linesY: i, line: line}
+ v.viewLines = append(v.viewLines, vline)
+ continue
+ } else {
+ for n := 0; n <= len(line); n += maxX {
+ if len(line[n:]) <= maxX {
+ vline := viewLine{linesX: n, linesY: i, line: line[n:]}
+ v.viewLines = append(v.viewLines, vline)
+ } else {
+ vline := viewLine{linesX: n, linesY: i, line: line[n : n+maxX]}
+ v.viewLines = append(v.viewLines, vline)
+ }
+ }
+ }
+ } else {
+ vline := viewLine{linesX: 0, linesY: i, line: line}
+ v.viewLines = append(v.viewLines, vline)
+ }
+ }
+ v.tainted = false
+ }
+
+ if v.Autoscroll && len(v.viewLines) > maxY {
+ v.oy = len(v.viewLines) - maxY
+ }
+ y := 0
+ for i, vline := range v.viewLines {
+ if i < v.oy {
+ continue
+ }
+ if y >= maxY {
+ break
+ }
+ x := 0
+ for j, c := range vline.line {
+ if j < v.ox {
+ continue
+ }
+ if x >= maxX {
+ break
+ }
+
+ fgColor := c.fgColor
+ if fgColor == ColorDefault {
+ fgColor = v.FgColor
+ }
+ bgColor := c.bgColor
+ if bgColor == ColorDefault {
+ bgColor = v.BgColor
+ }
+
+ if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
+ return err
+ }
+ x++
+ }
+ y++
+ }
+ return nil
+}
+
+// realPosition returns the position in the internal buffer corresponding to the
+// point (x, y) of the view.
+func (v *View) realPosition(vx, vy int) (x, y int, err error) {
+ vx = v.ox + vx
+ vy = v.oy + vy
+
+ if vx < 0 || vy < 0 {
+ return 0, 0, errors.New("invalid point")
+ }
+
+ if len(v.viewLines) == 0 {
+ return vx, vy, nil
+ }
+
+ if vy < len(v.viewLines) {
+ vline := v.viewLines[vy]
+ x = vline.linesX + vx
+ y = vline.linesY
+ } else {
+ vline := v.viewLines[len(v.viewLines)-1]
+ x = vx
+ y = vline.linesY + vy - len(v.viewLines) + 1
+ }
+
+ return x, y, nil
+}
+
+// Clear empties the view's internal buffer.
+func (v *View) Clear() {
+ v.tainted = true
+
+ v.lines = nil
+ v.viewLines = nil
+ v.readOffset = 0
+ v.clearRunes()
+}
+
+// clearRunes erases all the cells in the view.
+func (v *View) clearRunes() {
+ maxX, maxY := v.Size()
+ for x := 0; x < maxX; x++ {
+ for y := 0; y < maxY; y++ {
+ termbox.SetCell(v.x0+x+1, v.y0+y+1, ' ',
+ termbox.Attribute(v.FgColor), termbox.Attribute(v.BgColor))
+ }
+ }
+}
+
+// BufferLines returns the lines in the view's internal
+// buffer.
+func (v *View) BufferLines() []string {
+ lines := make([]string, len(v.lines))
+ for i, l := range v.lines {
+ str := lineType(l).String()
+ str = strings.Replace(str, "\x00", " ", -1)
+ lines[i] = str
+ }
+ return lines
+}
+
+// Buffer returns a string with the contents of the view's internal
+// buffer.
+func (v *View) Buffer() string {
+ str := ""
+ for _, l := range v.lines {
+ str += lineType(l).String() + "\n"
+ }
+ return strings.Replace(str, "\x00", " ", -1)
+}
+
+// ViewBufferLines returns the lines in the view's internal
+// buffer that is shown to the user.
+func (v *View) ViewBufferLines() []string {
+ lines := make([]string, len(v.viewLines))
+ for i, l := range v.viewLines {
+ str := lineType(l.line).String()
+ str = strings.Replace(str, "\x00", " ", -1)
+ lines[i] = str
+ }
+ return lines
+}
+
+// ViewBuffer returns a string with the contents of the view's buffer that is
+// shown to the user.
+func (v *View) ViewBuffer() string {
+ str := ""
+ for _, l := range v.viewLines {
+ str += lineType(l.line).String() + "\n"
+ }
+ return strings.Replace(str, "\x00", " ", -1)
+}
+
+// Line returns a string with the line of the view's internal buffer
+// at the position corresponding to the point (x, y).
+func (v *View) Line(y int) (string, error) {
+ _, y, err := v.realPosition(0, y)
+ if err != nil {
+ return "", err
+ }
+
+ if y < 0 || y >= len(v.lines) {
+ return "", errors.New("invalid point")
+ }
+
+ return lineType(v.lines[y]).String(), nil
+}
+
+// Word returns a string with the word of the view's internal buffer
+// at the position corresponding to the point (x, y).
+func (v *View) Word(x, y int) (string, error) {
+ x, y, err := v.realPosition(x, y)
+ if err != nil {
+ return "", err
+ }
+
+ if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
+ return "", errors.New("invalid point")
+ }
+
+ str := lineType(v.lines[y]).String()
+
+ nl := strings.LastIndexFunc(str[:x], indexFunc)
+ if nl == -1 {
+ nl = 0
+ } else {
+ nl = nl + 1
+ }
+ nr := strings.IndexFunc(str[x:], indexFunc)
+ if nr == -1 {
+ nr = len(str)
+ } else {
+ nr = nr + x
+ }
+ return string(str[nl:nr]), nil
+}
+
+// indexFunc allows to split lines by words taking into account spaces
+// and 0.
+func indexFunc(r rune) bool {
+ return r == ' ' || r == 0
+}
diff --git a/pkg/slice/slice.go b/pkg/slice/slice.go
new file mode 100644
index 0000000..150d38a
--- /dev/null
+++ b/pkg/slice/slice.go
@@ -0,0 +1,40 @@
+// Package slice provides a slice sorting function.
+package slice
+
+import (
+ "fmt"
+ "reflect"
+ "sort"
+
+ "go4.org/reflectutil"
+)
+
+// Sort sorts the provided slice using the function less.
+// If slice is not a slice, Sort panics.
+func Sort(slice interface{}, less func(i, j int) bool) {
+ sort.Sort(SortInterface(slice, less))
+}
+
+// SortInterface returns a sort.Interface to sort the provided slice
+// using the function less.
+func SortInterface(slice interface{}, less func(i, j int) bool) sort.Interface {
+ sv := reflect.ValueOf(slice)
+ if sv.Kind() != reflect.Slice {
+ panic(fmt.Sprintf("slice.Sort called with non-slice value of type %T", slice))
+ }
+ return &funcs{
+ length: sv.Len(),
+ less: less,
+ swap: reflectutil.Swapper(slice),
+ }
+}
+
+type funcs struct {
+ length int
+ less func(i, j int) bool
+ swap func(i, j int)
+}
+
+func (f *funcs) Len() int { return f.length }
+func (f *funcs) Less(i, j int) bool { return f.less(i, j) }
+func (f *funcs) Swap(i, j int) { f.swap(i, j) }
diff --git a/vendor/github.com/BurntSushi/toml/.gitignore b/vendor/github.com/BurntSushi/toml/.gitignore
new file mode 100644
index 0000000..0cd3800
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/.gitignore
@@ -0,0 +1,5 @@
+TAGS
+tags
+.*.swp
+tomlcheck/tomlcheck
+toml.test
diff --git a/vendor/github.com/BurntSushi/toml/.travis.yml b/vendor/github.com/BurntSushi/toml/.travis.yml
new file mode 100644
index 0000000..8b8afc4
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/.travis.yml
@@ -0,0 +1,15 @@
+language: go
+go:
+ - 1.1
+ - 1.2
+ - 1.3
+ - 1.4
+ - 1.5
+ - 1.6
+ - tip
+install:
+ - go install ./...
+ - go get github.com/BurntSushi/toml-test
+script:
+ - export PATH="$PATH:$HOME/gopath/bin"
+ - make test
diff --git a/vendor/github.com/BurntSushi/toml/COMPATIBLE b/vendor/github.com/BurntSushi/toml/COMPATIBLE
new file mode 100644
index 0000000..6efcfd0
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/COMPATIBLE
@@ -0,0 +1,3 @@
+Compatible with TOML version
+[v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md)
+
diff --git a/vendor/github.com/BurntSushi/toml/COPYING b/vendor/github.com/BurntSushi/toml/COPYING
new file mode 100644
index 0000000..5a8e332
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/COPYING
@@ -0,0 +1,14 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
+
diff --git a/vendor/github.com/BurntSushi/toml/Makefile b/vendor/github.com/BurntSushi/toml/Makefile
new file mode 100644
index 0000000..3600848
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/Makefile
@@ -0,0 +1,19 @@
+install:
+ go install ./...
+
+test: install
+ go test -v
+ toml-test toml-test-decoder
+ toml-test -encoder toml-test-encoder
+
+fmt:
+ gofmt -w *.go */*.go
+ colcheck *.go */*.go
+
+tags:
+ find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS
+
+push:
+ git push origin master
+ git push github master
+
diff --git a/vendor/github.com/BurntSushi/toml/README.md b/vendor/github.com/BurntSushi/toml/README.md
new file mode 100644
index 0000000..7c1b37e
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/README.md
@@ -0,0 +1,218 @@
+## TOML parser and encoder for Go with reflection
+
+TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
+reflection interface similar to Go's standard library `json` and `xml`
+packages. This package also supports the `encoding.TextUnmarshaler` and
+`encoding.TextMarshaler` interfaces so that you can define custom data
+representations. (There is an example of this below.)
+
+Spec: https://github.com/toml-lang/toml
+
+Compatible with TOML version
+[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
+
+Documentation: https://godoc.org/github.com/BurntSushi/toml
+
+Installation:
+
+```bash
+go get github.com/BurntSushi/toml
+```
+
+Try the toml validator:
+
+```bash
+go get github.com/BurntSushi/toml/cmd/tomlv
+tomlv some-toml-file.toml
+```
+
+[![Build Status](https://travis-ci.org/BurntSushi/toml.svg?branch=master)](https://travis-ci.org/BurntSushi/toml) [![GoDoc](https://godoc.org/github.com/BurntSushi/toml?status.svg)](https://godoc.org/github.com/BurntSushi/toml)
+
+### Testing
+
+This package passes all tests in
+[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder
+and the encoder.
+
+### Examples
+
+This package works similarly to how the Go standard library handles `XML`
+and `JSON`. Namely, data is loaded into Go values via reflection.
+
+For the simplest example, consider some TOML file as just a list of keys
+and values:
+
+```toml
+Age = 25
+Cats = [ "Cauchy", "Plato" ]
+Pi = 3.14
+Perfection = [ 6, 28, 496, 8128 ]
+DOB = 1987-07-05T05:45:00Z
+```
+
+Which could be defined in Go as:
+
+```go
+type Config struct {
+ Age int
+ Cats []string
+ Pi float64
+ Perfection []int
+ DOB time.Time // requires `import time`
+}
+```
+
+And then decoded with:
+
+```go
+var conf Config
+if _, err := toml.Decode(tomlData, &conf); err != nil {
+ // handle error
+}
+```
+
+You can also use struct tags if your struct field name doesn't map to a TOML
+key value directly:
+
+```toml
+some_key_NAME = "wat"
+```
+
+```go
+type TOML struct {
+ ObscureKey string `toml:"some_key_NAME"`
+}
+```
+
+### Using the `encoding.TextUnmarshaler` interface
+
+Here's an example that automatically parses duration strings into
+`time.Duration` values:
+
+```toml
+[[song]]
+name = "Thunder Road"
+duration = "4m49s"
+
+[[song]]
+name = "Stairway to Heaven"
+duration = "8m03s"
+```
+
+Which can be decoded with:
+
+```go
+type song struct {
+ Name string
+ Duration duration
+}
+type songs struct {
+ Song []song
+}
+var favorites songs
+if _, err := toml.Decode(blob, &favorites); err != nil {
+ log.Fatal(err)
+}
+
+for _, s := range favorites.Song {
+ fmt.Printf("%s (%s)\n", s.Name, s.Duration)
+}
+```
+
+And you'll also need a `duration` type that satisfies the
+`encoding.TextUnmarshaler` interface:
+
+```go
+type duration struct {
+ time.Duration
+}
+
+func (d *duration) UnmarshalText(text []byte) error {
+ var err error
+ d.Duration, err = time.ParseDuration(string(text))
+ return err
+}
+```
+
+### More complex usage
+
+Here's an example of how to load the example from the official spec page:
+
+```toml
+# This is a TOML document. Boom.
+
+title = "TOML Example"
+
+[owner]
+name = "Tom Preston-Werner"
+organization = "GitHub"
+bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
+dob = 1979-05-27T07:32:00Z # First class dates? Why not?
+
+[database]
+server = "192.168.1.1"
+ports = [ 8001, 8001, 8002 ]
+connection_max = 5000
+enabled = true
+
+[servers]
+
+ # You can indent as you please. Tabs or spaces. TOML don't care.
+ [servers.alpha]
+ ip = "10.0.0.1"
+ dc = "eqdc10"
+
+ [servers.beta]
+ ip = "10.0.0.2"
+ dc = "eqdc10"
+
+[clients]
+data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
+
+# Line breaks are OK when inside arrays
+hosts = [
+ "alpha",
+ "omega"
+]
+```
+
+And the corresponding Go types are:
+
+```go
+type tomlConfig struct {
+ Title string
+ Owner ownerInfo
+ DB database `toml:"database"`
+ Servers map[string]server
+ Clients clients
+}
+
+type ownerInfo struct {
+ Name string
+ Org string `toml:"organization"`
+ Bio string
+ DOB time.Time
+}
+
+type database struct {
+ Server string
+ Ports []int
+ ConnMax int `toml:"connection_max"`
+ Enabled bool
+}
+
+type server struct {
+ IP string
+ DC string
+}
+
+type clients struct {
+ Data [][]interface{}
+ Hosts []string
+}
+```
+
+Note that a case insensitive match will be tried if an exact match can't be
+found.
+
+A working example of the above can be found in `_examples/example.{go,toml}`.
diff --git a/vendor/github.com/BurntSushi/toml/_examples/example.go b/vendor/github.com/BurntSushi/toml/_examples/example.go
new file mode 100644
index 0000000..79f31f2
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/_examples/example.go
@@ -0,0 +1,61 @@
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/BurntSushi/toml"
+)
+
+type tomlConfig struct {
+ Title string
+ Owner ownerInfo
+ DB database `toml:"database"`
+ Servers map[string]server
+ Clients clients
+}
+
+type ownerInfo struct {
+ Name string
+ Org string `toml:"organization"`
+ Bio string
+ DOB time.Time
+}
+
+type database struct {
+ Server string
+ Ports []int
+ ConnMax int `toml:"connection_max"`
+ Enabled bool
+}
+
+type server struct {
+ IP string
+ DC string
+}
+
+type clients struct {
+ Data [][]interface{}
+ Hosts []string
+}
+
+func main() {
+ var config tomlConfig
+ if _, err := toml.DecodeFile("example.toml", &config); err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ fmt.Printf("Title: %s\n", config.Title)
+ fmt.Printf("Owner: %s (%s, %s), Born: %s\n",
+ config.Owner.Name, config.Owner.Org, config.Owner.Bio,
+ config.Owner.DOB)
+ fmt.Printf("Database: %s %v (Max conn. %d), Enabled? %v\n",
+ config.DB.Server, config.DB.Ports, config.DB.ConnMax,
+ config.DB.Enabled)
+ for serverName, server := range config.Servers {
+ fmt.Printf("Server: %s (%s, %s)\n", serverName, server.IP, server.DC)
+ }
+ fmt.Printf("Client data: %v\n", config.Clients.Data)
+ fmt.Printf("Client hosts: %v\n", config.Clients.Hosts)
+}
diff --git a/vendor/github.com/BurntSushi/toml/_examples/example.toml b/vendor/github.com/BurntSushi/toml/_examples/example.toml
new file mode 100644
index 0000000..32c7a4f
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/_examples/example.toml
@@ -0,0 +1,35 @@
+# This is a TOML document. Boom.
+
+title = "TOML Example"
+
+[owner]
+name = "Tom Preston-Werner"
+organization = "GitHub"
+bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
+dob = 1979-05-27T07:32:00Z # First class dates? Why not?
+
+[database]
+server = "192.168.1.1"
+ports = [ 8001, 8001, 8002 ]
+connection_max = 5000
+enabled = true
+
+[servers]
+
+ # You can indent as you please. Tabs or spaces. TOML don't care.
+ [servers.alpha]
+ ip = "10.0.0.1"
+ dc = "eqdc10"
+
+ [servers.beta]
+ ip = "10.0.0.2"
+ dc = "eqdc10"
+
+[clients]
+data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
+
+# Line breaks are OK when inside arrays
+hosts = [
+ "alpha",
+ "omega"
+]
diff --git a/vendor/github.com/BurntSushi/toml/_examples/hard.toml b/vendor/github.com/BurntSushi/toml/_examples/hard.toml
new file mode 100644
index 0000000..26145d2
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/_examples/hard.toml
@@ -0,0 +1,22 @@
+# Test file for TOML
+# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate
+# This part you'll really hate
+
+[the]
+test_string = "You'll hate me after this - #" # " Annoying, isn't it?
+
+ [the.hard]
+ test_array = [ "] ", " # "] # ] There you go, parse this!
+ test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
+ # You didn't think it'd as easy as chucking out the last #, did you?
+ another_test_string = " Same thing, but with a string #"
+ harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too"
+ # Things will get harder
+
+ [the.hard.bit#]
+ what? = "You don't think some user won't do that?"
+ multi_line_array = [
+ "]",
+ # ] Oh yes I did
+ ]
+
diff --git a/vendor/github.com/BurntSushi/toml/_examples/implicit.toml b/vendor/github.com/BurntSushi/toml/_examples/implicit.toml
new file mode 100644
index 0000000..1dea5ce
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/_examples/implicit.toml
@@ -0,0 +1,4 @@
+# [x] you
+# [x.y] don't
+# [x.y.z] need these
+[x.y.z.w] # for this to work
diff --git a/vendor/github.com/BurntSushi/toml/_examples/invalid-apples.toml b/vendor/github.com/BurntSushi/toml/_examples/invalid-apples.toml
new file mode 100644
index 0000000..74e9e33
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/_examples/invalid-apples.toml
@@ -0,0 +1,6 @@
+# DO NOT WANT
+[fruit]
+type = "apple"
+
+[fruit.type]
+apple = "yes"
diff --git a/vendor/github.com/BurntSushi/toml/_examples/invalid.toml b/vendor/github.com/BurntSushi/toml/_examples/invalid.toml
new file mode 100644
index 0000000..beb1dba
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/_examples/invalid.toml
@@ -0,0 +1,35 @@
+# This is an INVALID TOML document. Boom.
+# Can you spot the error without help?
+
+title = "TOML Example"
+
+[owner]
+name = "Tom Preston-Werner"
+organization = "GitHub"
+bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
+dob = 1979-05-27T7:32:00Z # First class dates? Why not?
+
+[database]
+server = "192.168.1.1"
+ports = [ 8001, 8001, 8002 ]
+connection_max = 5000
+enabled = true
+
+[servers]
+ # You can indent as you please. Tabs or spaces. TOML don't care.
+ [servers.alpha]
+ ip = "10.0.0.1"
+ dc = "eqdc10"
+
+ [servers.beta]
+ ip = "10.0.0.2"
+ dc = "eqdc10"
+
+[clients]
+data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
+
+# Line breaks are OK when inside arrays
+hosts = [
+ "alpha",
+ "omega"
+]
diff --git a/vendor/github.com/BurntSushi/toml/_examples/readme1.toml b/vendor/github.com/BurntSushi/toml/_examples/readme1.toml
new file mode 100644
index 0000000..3e1261d
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/_examples/readme1.toml
@@ -0,0 +1,5 @@
+Age = 25
+Cats = [ "Cauchy", "Plato" ]
+Pi = 3.14
+Perfection = [ 6, 28, 496, 8128 ]
+DOB = 1987-07-05T05:45:00Z
diff --git a/vendor/github.com/BurntSushi/toml/_examples/readme2.toml b/vendor/github.com/BurntSushi/toml/_examples/readme2.toml
new file mode 100644
index 0000000..b51cd93
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/_examples/readme2.toml
@@ -0,0 +1 @@
+some_key_NAME = "wat"
diff --git a/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING b/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING
new file mode 100644
index 0000000..5a8e332
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING
@@ -0,0 +1,14 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
+
diff --git a/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/README.md b/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/README.md
new file mode 100644
index 0000000..93f4e3a
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/README.md
@@ -0,0 +1,13 @@
+# Implements the TOML test suite interface
+
+This is an implementation of the interface expected by
+[toml-test](https://github.com/BurntSushi/toml-test) for my
+[toml parser written in Go](https://github.com/BurntSushi/toml).
+In particular, it maps TOML data on `stdin` to a JSON format on `stdout`.
+
+
+Compatible with TOML version
+[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
+
+Compatible with `toml-test` version
+[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)
diff --git a/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/main.go b/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/main.go
new file mode 100644
index 0000000..14e7557
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/main.go
@@ -0,0 +1,90 @@
+// Command toml-test-decoder satisfies the toml-test interface for testing
+// TOML decoders. Namely, it accepts TOML on stdin and outputs JSON on stdout.
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "path"
+ "time"
+
+ "github.com/BurntSushi/toml"
+)
+
+func init() {
+ log.SetFlags(0)
+
+ flag.Usage = usage
+ flag.Parse()
+}
+
+func usage() {
+ log.Printf("Usage: %s < toml-file\n", path.Base(os.Args[0]))
+ flag.PrintDefaults()
+
+ os.Exit(1)
+}
+
+func main() {
+ if flag.NArg() != 0 {
+ flag.Usage()
+ }
+
+ var tmp interface{}
+ if _, err := toml.DecodeReader(os.Stdin, &tmp); err != nil {
+ log.Fatalf("Error decoding TOML: %s", err)
+ }
+
+ typedTmp := translate(tmp)
+ if err := json.NewEncoder(os.Stdout).Encode(typedTmp); err != nil {
+ log.Fatalf("Error encoding JSON: %s", err)
+ }
+}
+
+func translate(tomlData interface{}) interface{} {
+ switch orig := tomlData.(type) {
+ case map[string]interface{}:
+ typed := make(map[string]interface{}, len(orig))
+ for k, v := range orig {
+ typed[k] = translate(v)
+ }
+ return typed
+ case []map[string]interface{}:
+ typed := make([]map[string]interface{}, len(orig))
+ for i, v := range orig {
+ typed[i] = translate(v).(map[string]interface{})
+ }
+ return typed
+ case []interface{}:
+ typed := make([]interface{}, len(orig))
+ for i, v := range orig {
+ typed[i] = translate(v)
+ }
+
+ // We don't really need to tag arrays, but let's be future proof.
+ // (If TOML ever supports tuples, we'll need this.)
+ return tag("array", typed)
+ case time.Time:
+ return tag("datetime", orig.Format("2006-01-02T15:04:05Z"))
+ case bool:
+ return tag("bool", fmt.Sprintf("%v", orig))
+ case int64:
+ return tag("integer", fmt.Sprintf("%d", orig))
+ case float64:
+ return tag("float", fmt.Sprintf("%v", orig))
+ case string:
+ return tag("string", orig)
+ }
+
+ panic(fmt.Sprintf("Unknown type: %T", tomlData))
+}
+
+func tag(typeName string, data interface{}) map[string]interface{} {
+ return map[string]interface{}{
+ "type": typeName,
+ "value": data,
+ }
+}
diff --git a/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING b/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING
new file mode 100644
index 0000000..5a8e332
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING
@@ -0,0 +1,14 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
+
diff --git a/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/README.md b/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/README.md
new file mode 100644
index 0000000..a45bd4d
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/README.md
@@ -0,0 +1,13 @@
+# Implements the TOML test suite interface for TOML encoders
+
+This is an implementation of the interface expected by
+[toml-test](https://github.com/BurntSushi/toml-test) for the
+[TOML encoder](https://github.com/BurntSushi/toml).
+In particular, it maps JSON data on `stdin` to a TOML format on `stdout`.
+
+
+Compatible with TOML version
+[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
+
+Compatible with `toml-test` version
+[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)
diff --git a/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/main.go b/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/main.go
new file mode 100644
index 0000000..092cc68
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/main.go
@@ -0,0 +1,131 @@
+// Command toml-test-encoder satisfies the toml-test interface for testing
+// TOML encoders. Namely, it accepts JSON on stdin and outputs TOML on stdout.
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "log"
+ "os"
+ "path"
+ "strconv"
+ "time"
+
+ "github.com/BurntSushi/toml"
+)
+
+func init() {
+ log.SetFlags(0)
+
+ flag.Usage = usage
+ flag.Parse()
+}
+
+func usage() {
+ log.Printf("Usage: %s < json-file\n", path.Base(os.Args[0]))
+ flag.PrintDefaults()
+
+ os.Exit(1)
+}
+
+func main() {
+ if flag.NArg() != 0 {
+ flag.Usage()
+ }
+
+ var tmp interface{}
+ if err := json.NewDecoder(os.Stdin).Decode(&tmp); err != nil {
+ log.Fatalf("Error decoding JSON: %s", err)
+ }
+
+ tomlData := translate(tmp)
+ if err := toml.NewEncoder(os.Stdout).Encode(tomlData); err != nil {
+ log.Fatalf("Error encoding TOML: %s", err)
+ }
+}
+
+func translate(typedJson interface{}) interface{} {
+ switch v := typedJson.(type) {
+ case map[string]interface{}:
+ if len(v) == 2 && in("type", v) && in("value", v) {
+ return untag(v)
+ }
+ m := make(map[string]interface{}, len(v))
+ for k, v2 := range v {
+ m[k] = translate(v2)
+ }
+ return m
+ case []interface{}:
+ tabArray := make([]map[string]interface{}, len(v))
+ for i := range v {
+ if m, ok := translate(v[i]).(map[string]interface{}); ok {
+ tabArray[i] = m
+ } else {
+ log.Fatalf("JSON arrays may only contain objects. This " +
+ "corresponds to only tables being allowed in " +
+ "TOML table arrays.")
+ }
+ }
+ return tabArray
+ }
+ log.Fatalf("Unrecognized JSON format '%T'.", typedJson)
+ panic("unreachable")
+}
+
+func untag(typed map[string]interface{}) interface{} {
+ t := typed["type"].(string)
+ v := typed["value"]
+ switch t {
+ case "string":
+ return v.(string)
+ case "integer":
+ v := v.(string)
+ n, err := strconv.Atoi(v)
+ if err != nil {
+ log.Fatalf("Could not parse '%s' as integer: %s", v, err)
+ }
+ return n
+ case "float":
+ v := v.(string)
+ f, err := strconv.ParseFloat(v, 64)
+ if err != nil {
+ log.Fatalf("Could not parse '%s' as float64: %s", v, err)
+ }
+ return f
+ case "datetime":
+ v := v.(string)
+ t, err := time.Parse("2006-01-02T15:04:05Z", v)
+ if err != nil {
+ log.Fatalf("Could not parse '%s' as a datetime: %s", v, err)
+ }
+ return t
+ case "bool":
+ v := v.(string)
+ switch v {
+ case "true":
+ return true
+ case "false":
+ return false
+ }
+ log.Fatalf("Could not parse '%s' as a boolean.", v)
+ case "array":
+ v := v.([]interface{})
+ array := make([]interface{}, len(v))
+ for i := range v {
+ if m, ok := v[i].(map[string]interface{}); ok {
+ array[i] = untag(m)
+ } else {
+ log.Fatalf("Arrays may only contain other arrays or "+
+ "primitive values, but found a '%T'.", m)
+ }
+ }
+ return array
+ }
+ log.Fatalf("Unrecognized tag type '%s'.", t)
+ panic("unreachable")
+}
+
+func in(key string, m map[string]interface{}) bool {
+ _, ok := m[key]
+ return ok
+}
diff --git a/vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING b/vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING
new file mode 100644
index 0000000..5a8e332
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING
@@ -0,0 +1,14 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
+
diff --git a/vendor/github.com/BurntSushi/toml/cmd/tomlv/README.md b/vendor/github.com/BurntSushi/toml/cmd/tomlv/README.md
new file mode 100644
index 0000000..51231e2
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/cmd/tomlv/README.md
@@ -0,0 +1,21 @@
+# TOML Validator
+
+If Go is installed, it's simple to try it out:
+
+```bash
+go get github.com/BurntSushi/toml/cmd/tomlv
+tomlv some-toml-file.toml
+```
+
+You can see the types of every key in a TOML file with:
+
+```bash
+tomlv -types some-toml-file.toml
+```
+
+At the moment, only one error message is reported at a time. Error messages
+include line numbers. No output means that the files given are valid TOML, or
+there is a bug in `tomlv`.
+
+Compatible with TOML version
+[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
diff --git a/vendor/github.com/BurntSushi/toml/cmd/tomlv/main.go b/vendor/github.com/BurntSushi/toml/cmd/tomlv/main.go
new file mode 100644
index 0000000..c7d689a
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/cmd/tomlv/main.go
@@ -0,0 +1,61 @@
+// Command tomlv validates TOML documents and prints each key's type.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "path"
+ "strings"
+ "text/tabwriter"
+
+ "github.com/BurntSushi/toml"
+)
+
+var (
+ flagTypes = false
+)
+
+func init() {
+ log.SetFlags(0)
+
+ flag.BoolVar(&flagTypes, "types", flagTypes,
+ "When set, the types of every defined key will be shown.")
+
+ flag.Usage = usage
+ flag.Parse()
+}
+
+func usage() {
+ log.Printf("Usage: %s toml-file [ toml-file ... ]\n",
+ path.Base(os.Args[0]))
+ flag.PrintDefaults()
+
+ os.Exit(1)
+}
+
+func main() {
+ if flag.NArg() < 1 {
+ flag.Usage()
+ }
+ for _, f := range flag.Args() {
+ var tmp interface{}
+ md, err := toml.DecodeFile(f, &tmp)
+ if err != nil {
+ log.Fatalf("Error in '%s': %s", f, err)
+ }
+ if flagTypes {
+ printTypes(md)
+ }
+ }
+}
+
+func printTypes(md toml.MetaData) {
+ tabw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
+ for _, key := range md.Keys() {
+ fmt.Fprintf(tabw, "%s%s\t%s\n",
+ strings.Repeat(" ", len(key)-1), key, md.Type(key...))
+ }
+ tabw.Flush()
+}
diff --git a/vendor/github.com/BurntSushi/toml/decode.go b/vendor/github.com/BurntSushi/toml/decode.go
new file mode 100644
index 0000000..b0fd51d
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/decode.go
@@ -0,0 +1,509 @@
+package toml
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "math"
+ "reflect"
+ "strings"
+ "time"
+)
+
+func e(format string, args ...interface{}) error {
+ return fmt.Errorf("toml: "+format, args...)
+}
+
+// Unmarshaler is the interface implemented by objects that can unmarshal a
+// TOML description of themselves.
+type Unmarshaler interface {
+ UnmarshalTOML(interface{}) error
+}
+
+// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`.
+func Unmarshal(p []byte, v interface{}) error {
+ _, err := Decode(string(p), v)
+ return err
+}
+
+// Primitive is a TOML value that hasn't been decoded into a Go value.
+// When using the various `Decode*` functions, the type `Primitive` may
+// be given to any value, and its decoding will be delayed.
+//
+// A `Primitive` value can be decoded using the `PrimitiveDecode` function.
+//
+// The underlying representation of a `Primitive` value is subject to change.
+// Do not rely on it.
+//
+// N.B. Primitive values are still parsed, so using them will only avoid
+// the overhead of reflection. They can be useful when you don't know the
+// exact type of TOML data until run time.
+type Primitive struct {
+ undecoded interface{}
+ context Key
+}
+
+// DEPRECATED!
+//
+// Use MetaData.PrimitiveDecode instead.
+func PrimitiveDecode(primValue Primitive, v interface{}) error {
+ md := MetaData{decoded: make(map[string]bool)}
+ return md.unify(primValue.undecoded, rvalue(v))
+}
+
+// PrimitiveDecode is just like the other `Decode*` functions, except it
+// decodes a TOML value that has already been parsed. Valid primitive values
+// can *only* be obtained from values filled by the decoder functions,
+// including this method. (i.e., `v` may contain more `Primitive`
+// values.)
+//
+// Meta data for primitive values is included in the meta data returned by
+// the `Decode*` functions with one exception: keys returned by the Undecoded
+// method will only reflect keys that were decoded. Namely, any keys hidden
+// behind a Primitive will be considered undecoded. Executing this method will
+// update the undecoded keys in the meta data. (See the example.)
+func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
+ md.context = primValue.context
+ defer func() { md.context = nil }()
+ return md.unify(primValue.undecoded, rvalue(v))
+}
+
+// Decode will decode the contents of `data` in TOML format into a pointer
+// `v`.
+//
+// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be
+// used interchangeably.)
+//
+// TOML arrays of tables correspond to either a slice of structs or a slice
+// of maps.
+//
+// TOML datetimes correspond to Go `time.Time` values.
+//
+// All other TOML types (float, string, int, bool and array) correspond
+// to the obvious Go types.
+//
+// An exception to the above rules is if a type implements the
+// encoding.TextUnmarshaler interface. In this case, any primitive TOML value
+// (floats, strings, integers, booleans and datetimes) will be converted to
+// a byte string and given to the value's UnmarshalText method. See the
+// Unmarshaler example for a demonstration with time duration strings.
+//
+// Key mapping
+//
+// TOML keys can map to either keys in a Go map or field names in a Go
+// struct. The special `toml` struct tag may be used to map TOML keys to
+// struct fields that don't match the key name exactly. (See the example.)
+// A case insensitive match to struct names will be tried if an exact match
+// can't be found.
+//
+// The mapping between TOML values and Go values is loose. That is, there
+// may exist TOML values that cannot be placed into your representation, and
+// there may be parts of your representation that do not correspond to
+// TOML values. This loose mapping can be made stricter by using the IsDefined
+// and/or Undecoded methods on the MetaData returned.
+//
+// This decoder will not handle cyclic types. If a cyclic type is passed,
+// `Decode` will not terminate.
+func Decode(data string, v interface{}) (MetaData, error) {
+ rv := reflect.ValueOf(v)
+ if rv.Kind() != reflect.Ptr {
+ return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
+ }
+ if rv.IsNil() {
+ return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
+ }
+ p, err := parse(data)
+ if err != nil {
+ return MetaData{}, err
+ }
+ md := MetaData{
+ p.mapping, p.types, p.ordered,
+ make(map[string]bool, len(p.ordered)), nil,
+ }
+ return md, md.unify(p.mapping, indirect(rv))
+}
+
+// DecodeFile is just like Decode, except it will automatically read the
+// contents of the file at `fpath` and decode it for you.
+func DecodeFile(fpath string, v interface{}) (MetaData, error) {
+ bs, err := ioutil.ReadFile(fpath)
+ if err != nil {
+ return MetaData{}, err
+ }
+ return Decode(string(bs), v)
+}
+
+// DecodeReader is just like Decode, except it will consume all bytes
+// from the reader and decode it for you.
+func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
+ bs, err := ioutil.ReadAll(r)
+ if err != nil {
+ return MetaData{}, err
+ }
+ return Decode(string(bs), v)
+}
+
+// unify performs a sort of type unification based on the structure of `rv`,
+// which is the client representation.
+//
+// Any type mismatch produces an error. Finding a type that we don't know
+// how to handle produces an unsupported type error.
+func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
+
+ // Special case. Look for a `Primitive` value.
+ if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() {
+ // Save the undecoded data and the key context into the primitive
+ // value.
+ context := make(Key, len(md.context))
+ copy(context, md.context)
+ rv.Set(reflect.ValueOf(Primitive{
+ undecoded: data,
+ context: context,
+ }))
+ return nil
+ }
+
+ // Special case. Unmarshaler Interface support.
+ if rv.CanAddr() {
+ if v, ok := rv.Addr().Interface().(Unmarshaler); ok {
+ return v.UnmarshalTOML(data)
+ }
+ }
+
+ // Special case. Handle time.Time values specifically.
+ // TODO: Remove this code when we decide to drop support for Go 1.1.
+ // This isn't necessary in Go 1.2 because time.Time satisfies the encoding
+ // interfaces.
+ if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) {
+ return md.unifyDatetime(data, rv)
+ }
+
+ // Special case. Look for a value satisfying the TextUnmarshaler interface.
+ if v, ok := rv.Interface().(TextUnmarshaler); ok {
+ return md.unifyText(data, v)
+ }
+ // BUG(burntsushi)
+ // The behavior here is incorrect whenever a Go type satisfies the
+ // encoding.TextUnmarshaler interface but also corresponds to a TOML
+ // hash or array. In particular, the unmarshaler should only be applied
+ // to primitive TOML values. But at this point, it will be applied to
+ // all kinds of values and produce an incorrect error whenever those values
+ // are hashes or arrays (including arrays of tables).
+
+ k := rv.Kind()
+
+ // laziness
+ if k >= reflect.Int && k <= reflect.Uint64 {
+ return md.unifyInt(data, rv)
+ }
+ switch k {
+ case reflect.Ptr:
+ elem := reflect.New(rv.Type().Elem())
+ err := md.unify(data, reflect.Indirect(elem))
+ if err != nil {
+ return err
+ }
+ rv.Set(elem)
+ return nil
+ case reflect.Struct:
+ return md.unifyStruct(data, rv)
+ case reflect.Map:
+ return md.unifyMap(data, rv)
+ case reflect.Array:
+ return md.unifyArray(data, rv)
+ case reflect.Slice:
+ return md.unifySlice(data, rv)
+ case reflect.String:
+ return md.unifyString(data, rv)
+ case reflect.Bool:
+ return md.unifyBool(data, rv)
+ case reflect.Interface:
+ // we only support empty interfaces.
+ if rv.NumMethod() > 0 {
+ return e("unsupported type %s", rv.Type())
+ }
+ return md.unifyAnything(data, rv)
+ case reflect.Float32:
+ fallthrough
+ case reflect.Float64:
+ return md.unifyFloat64(data, rv)
+ }
+ return e("unsupported type %s", rv.Kind())
+}
+
+func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
+ tmap, ok := mapping.(map[string]interface{})
+ if !ok {
+ if mapping == nil {
+ return nil
+ }
+ return e("type mismatch for %s: expected table but found %T",
+ rv.Type().String(), mapping)
+ }
+
+ for key, datum := range tmap {
+ var f *field
+ fields := cachedTypeFields(rv.Type())
+ for i := range fields {
+ ff := &fields[i]
+ if ff.name == key {
+ f = ff
+ break
+ }
+ if f == nil && strings.EqualFold(ff.name, key) {
+ f = ff
+ }
+ }
+ if f != nil {
+ subv := rv
+ for _, i := range f.index {
+ subv = indirect(subv.Field(i))
+ }
+ if isUnifiable(subv) {
+ md.decoded[md.context.add(key).String()] = true
+ md.context = append(md.context, key)
+ if err := md.unify(datum, subv); err != nil {
+ return err
+ }
+ md.context = md.context[0 : len(md.context)-1]
+ } else if f.name != "" {
+ // Bad user! No soup for you!
+ return e("cannot write unexported field %s.%s",
+ rv.Type().String(), f.name)
+ }
+ }
+ }
+ return nil
+}
+
+func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
+ tmap, ok := mapping.(map[string]interface{})
+ if !ok {
+ if tmap == nil {
+ return nil
+ }
+ return badtype("map", mapping)
+ }
+ if rv.IsNil() {
+ rv.Set(reflect.MakeMap(rv.Type()))
+ }
+ for k, v := range tmap {
+ md.decoded[md.context.add(k).String()] = true
+ md.context = append(md.context, k)
+
+ rvkey := indirect(reflect.New(rv.Type().Key()))
+ rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
+ if err := md.unify(v, rvval); err != nil {
+ return err
+ }
+ md.context = md.context[0 : len(md.context)-1]
+
+ rvkey.SetString(k)
+ rv.SetMapIndex(rvkey, rvval)
+ }
+ return nil
+}
+
+func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
+ datav := reflect.ValueOf(data)
+ if datav.Kind() != reflect.Slice {
+ if !datav.IsValid() {
+ return nil
+ }
+ return badtype("slice", data)
+ }
+ sliceLen := datav.Len()
+ if sliceLen != rv.Len() {
+ return e("expected array length %d; got TOML array of length %d",
+ rv.Len(), sliceLen)
+ }
+ return md.unifySliceArray(datav, rv)
+}
+
+func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
+ datav := reflect.ValueOf(data)
+ if datav.Kind() != reflect.Slice {
+ if !datav.IsValid() {
+ return nil
+ }
+ return badtype("slice", data)
+ }
+ n := datav.Len()
+ if rv.IsNil() || rv.Cap() < n {
+ rv.Set(reflect.MakeSlice(rv.Type(), n, n))
+ }
+ rv.SetLen(n)
+ return md.unifySliceArray(datav, rv)
+}
+
+func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
+ sliceLen := data.Len()
+ for i := 0; i < sliceLen; i++ {
+ v := data.Index(i).Interface()
+ sliceval := indirect(rv.Index(i))
+ if err := md.unify(v, sliceval); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error {
+ if _, ok := data.(time.Time); ok {
+ rv.Set(reflect.ValueOf(data))
+ return nil
+ }
+ return badtype("time.Time", data)
+}
+
+func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
+ if s, ok := data.(string); ok {
+ rv.SetString(s)
+ return nil
+ }
+ return badtype("string", data)
+}
+
+func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
+ if num, ok := data.(float64); ok {
+ switch rv.Kind() {
+ case reflect.Float32:
+ fallthrough
+ case reflect.Float64:
+ rv.SetFloat(num)
+ default:
+ panic("bug")
+ }
+ return nil
+ }
+ return badtype("float", data)
+}
+
+func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
+ if num, ok := data.(int64); ok {
+ if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 {
+ switch rv.Kind() {
+ case reflect.Int, reflect.Int64:
+ // No bounds checking necessary.
+ case reflect.Int8:
+ if num < math.MinInt8 || num > math.MaxInt8 {
+ return e("value %d is out of range for int8", num)
+ }
+ case reflect.Int16:
+ if num < math.MinInt16 || num > math.MaxInt16 {
+ return e("value %d is out of range for int16", num)
+ }
+ case reflect.Int32:
+ if num < math.MinInt32 || num > math.MaxInt32 {
+ return e("value %d is out of range for int32", num)
+ }
+ }
+ rv.SetInt(num)
+ } else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 {
+ unum := uint64(num)
+ switch rv.Kind() {
+ case reflect.Uint, reflect.Uint64:
+ // No bounds checking necessary.
+ case reflect.Uint8:
+ if num < 0 || unum > math.MaxUint8 {
+ return e("value %d is out of range for uint8", num)
+ }
+ case reflect.Uint16:
+ if num < 0 || unum > math.MaxUint16 {
+ return e("value %d is out of range for uint16", num)
+ }
+ case reflect.Uint32:
+ if num < 0 || unum > math.MaxUint32 {
+ return e("value %d is out of range for uint32", num)
+ }
+ }
+ rv.SetUint(unum)
+ } else {
+ panic("unreachable")
+ }
+ return nil
+ }
+ return badtype("integer", data)
+}
+
+func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
+ if b, ok := data.(bool); ok {
+ rv.SetBool(b)
+ return nil
+ }
+ return badtype("boolean", data)
+}
+
+func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
+ rv.Set(reflect.ValueOf(data))
+ return nil
+}
+
+func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
+ var s string
+ switch sdata := data.(type) {
+ case TextMarshaler:
+ text, err := sdata.MarshalText()
+ if err != nil {
+ return err
+ }
+ s = string(text)
+ case fmt.Stringer:
+ s = sdata.String()
+ case string:
+ s = sdata
+ case bool:
+ s = fmt.Sprintf("%v", sdata)
+ case int64:
+ s = fmt.Sprintf("%d", sdata)
+ case float64:
+ s = fmt.Sprintf("%f", sdata)
+ default:
+ return badtype("primitive (string-like)", data)
+ }
+ if err := v.UnmarshalText([]byte(s)); err != nil {
+ return err
+ }
+ return nil
+}
+
+// rvalue returns a reflect.Value of `v`. All pointers are resolved.
+func rvalue(v interface{}) reflect.Value {
+ return indirect(reflect.ValueOf(v))
+}
+
+// indirect returns the value pointed to by a pointer.
+// Pointers are followed until the value is not a pointer.
+// New values are allocated for each nil pointer.
+//
+// An exception to this rule is if the value satisfies an interface of
+// interest to us (like encoding.TextUnmarshaler).
+func indirect(v reflect.Value) reflect.Value {
+ if v.Kind() != reflect.Ptr {
+ if v.CanSet() {
+ pv := v.Addr()
+ if _, ok := pv.Interface().(TextUnmarshaler); ok {
+ return pv
+ }
+ }
+ return v
+ }
+ if v.IsNil() {
+ v.Set(reflect.New(v.Type().Elem()))
+ }
+ return indirect(reflect.Indirect(v))
+}
+
+func isUnifiable(rv reflect.Value) bool {
+ if rv.CanSet() {
+ return true
+ }
+ if _, ok := rv.Interface().(TextUnmarshaler); ok {
+ return true
+ }
+ return false
+}
+
+func badtype(expected string, data interface{}) error {
+ return e("cannot load TOML value of type %T into a Go %s", data, expected)
+}
diff --git a/vendor/github.com/BurntSushi/toml/decode_meta.go b/vendor/github.com/BurntSushi/toml/decode_meta.go
new file mode 100644
index 0000000..b9914a6
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/decode_meta.go
@@ -0,0 +1,121 @@
+package toml
+
+import "strings"
+
+// MetaData allows access to meta information about TOML data that may not
+// be inferrable via reflection. In particular, whether a key has been defined
+// and the TOML type of a key.
+type MetaData struct {
+ mapping map[string]interface{}
+ types map[string]tomlType
+ keys []Key
+ decoded map[string]bool
+ context Key // Used only during decoding.
+}
+
+// IsDefined returns true if the key given exists in the TOML data. The key
+// should be specified hierarchially. e.g.,
+//
+// // access the TOML key 'a.b.c'
+// IsDefined("a", "b", "c")
+//
+// IsDefined will return false if an empty key given. Keys are case sensitive.
+func (md *MetaData) IsDefined(key ...string) bool {
+ if len(key) == 0 {
+ return false
+ }
+
+ var hash map[string]interface{}
+ var ok bool
+ var hashOrVal interface{} = md.mapping
+ for _, k := range key {
+ if hash, ok = hashOrVal.(map[string]interface{}); !ok {
+ return false
+ }
+ if hashOrVal, ok = hash[k]; !ok {
+ return false
+ }
+ }
+ return true
+}
+
+// Type returns a string representation of the type of the key specified.
+//
+// Type will return the empty string if given an empty key or a key that
+// does not exist. Keys are case sensitive.
+func (md *MetaData) Type(key ...string) string {
+ fullkey := strings.Join(key, ".")
+ if typ, ok := md.types[fullkey]; ok {
+ return typ.typeString()
+ }
+ return ""
+}
+
+// Key is the type of any TOML key, including key groups. Use (MetaData).Keys
+// to get values of this type.
+type Key []string
+
+func (k Key) String() string {
+ return strings.Join(k, ".")
+}
+
+func (k Key) maybeQuotedAll() string {
+ var ss []string
+ for i := range k {
+ ss = append(ss, k.maybeQuoted(i))
+ }
+ return strings.Join(ss, ".")
+}
+
+func (k Key) maybeQuoted(i int) string {
+ quote := false
+ for _, c := range k[i] {
+ if !isBareKeyChar(c) {
+ quote = true
+ break
+ }
+ }
+ if quote {
+ return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\""
+ }
+ return k[i]
+}
+
+func (k Key) add(piece string) Key {
+ newKey := make(Key, len(k)+1)
+ copy(newKey, k)
+ newKey[len(k)] = piece
+ return newKey
+}
+
+// Keys returns a slice of every key in the TOML data, including key groups.
+// Each key is itself a slice, where the first element is the top of the
+// hierarchy and the last is the most specific.
+//
+// The list will have the same order as the keys appeared in the TOML data.
+//
+// All keys returned are non-empty.
+func (md *MetaData) Keys() []Key {
+ return md.keys
+}
+
+// Undecoded returns all keys that have not been decoded in the order in which
+// they appear in the original TOML document.
+//
+// This includes keys that haven't been decoded because of a Primitive value.
+// Once the Primitive value is decoded, the keys will be considered decoded.
+//
+// Also note that decoding into an empty interface will result in no decoding,
+// and so no keys will be considered decoded.
+//
+// In this sense, the Undecoded keys correspond to keys in the TOML document
+// that do not have a concrete type in your representation.
+func (md *MetaData) Undecoded() []Key {
+ undecoded := make([]Key, 0, len(md.keys))
+ for _, key := range md.keys {
+ if !md.decoded[key.String()] {
+ undecoded = append(undecoded, key)
+ }
+ }
+ return undecoded
+}
diff --git a/vendor/github.com/BurntSushi/toml/decode_test.go b/vendor/github.com/BurntSushi/toml/decode_test.go
new file mode 100644
index 0000000..0c36b33
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/decode_test.go
@@ -0,0 +1,1447 @@
+package toml
+
+import (
+ "fmt"
+ "log"
+ "math"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+)
+
+func TestDecodeSimple(t *testing.T) {
+ var testSimple = `
+age = 250
+andrew = "gallant"
+kait = "brady"
+now = 1987-07-05T05:45:00Z
+yesOrNo = true
+pi = 3.14
+colors = [
+ ["red", "green", "blue"],
+ ["cyan", "magenta", "yellow", "black"],
+]
+
+[My.Cats]
+plato = "cat 1"
+cauchy = "cat 2"
+`
+
+ type cats struct {
+ Plato string
+ Cauchy string
+ }
+ type simple struct {
+ Age int
+ Colors [][]string
+ Pi float64
+ YesOrNo bool
+ Now time.Time
+ Andrew string
+ Kait string
+ My map[string]cats
+ }
+
+ var val simple
+ _, err := Decode(testSimple, &val)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ now, err := time.Parse("2006-01-02T15:04:05", "1987-07-05T05:45:00")
+ if err != nil {
+ panic(err)
+ }
+ var answer = simple{
+ Age: 250,
+ Andrew: "gallant",
+ Kait: "brady",
+ Now: now,
+ YesOrNo: true,
+ Pi: 3.14,
+ Colors: [][]string{
+ {"red", "green", "blue"},
+ {"cyan", "magenta", "yellow", "black"},
+ },
+ My: map[string]cats{
+ "Cats": {Plato: "cat 1", Cauchy: "cat 2"},
+ },
+ }
+ if !reflect.DeepEqual(val, answer) {
+ t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n",
+ answer, val)
+ }
+}
+
+func TestDecodeEmbedded(t *testing.T) {
+ type Dog struct{ Name string }
+ type Age int
+ type cat struct{ Name string }
+
+ for _, test := range []struct {
+ label string
+ input string
+ decodeInto interface{}
+ wantDecoded interface{}
+ }{
+ {
+ label: "embedded struct",
+ input: `Name = "milton"`,
+ decodeInto: &struct{ Dog }{},
+ wantDecoded: &struct{ Dog }{Dog{"milton"}},
+ },
+ {
+ label: "embedded non-nil pointer to struct",
+ input: `Name = "milton"`,
+ decodeInto: &struct{ *Dog }{},
+ wantDecoded: &struct{ *Dog }{&Dog{"milton"}},
+ },
+ {
+ label: "embedded nil pointer to struct",
+ input: ``,
+ decodeInto: &struct{ *Dog }{},
+ wantDecoded: &struct{ *Dog }{nil},
+ },
+ {
+ label: "unexported embedded struct",
+ input: `Name = "socks"`,
+ decodeInto: &struct{ cat }{},
+ wantDecoded: &struct{ cat }{cat{"socks"}},
+ },
+ {
+ label: "embedded int",
+ input: `Age = -5`,
+ decodeInto: &struct{ Age }{},
+ wantDecoded: &struct{ Age }{-5},
+ },
+ } {
+ _, err := Decode(test.input, test.decodeInto)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(test.wantDecoded, test.decodeInto) {
+ t.Errorf("%s: want decoded == %+v, got %+v",
+ test.label, test.wantDecoded, test.decodeInto)
+ }
+ }
+}
+
+func TestDecodeIgnoredFields(t *testing.T) {
+ type simple struct {
+ Number int `toml:"-"`
+ }
+ const input = `
+Number = 123
+- = 234
+`
+ var s simple
+ if _, err := Decode(input, &s); err != nil {
+ t.Fatal(err)
+ }
+ if s.Number != 0 {
+ t.Errorf("got: %d; want 0", s.Number)
+ }
+}
+
+func TestTableArrays(t *testing.T) {
+ var tomlTableArrays = `
+[[albums]]
+name = "Born to Run"
+
+ [[albums.songs]]
+ name = "Jungleland"
+
+ [[albums.songs]]
+ name = "Meeting Across the River"
+
+[[albums]]
+name = "Born in the USA"
+
+ [[albums.songs]]
+ name = "Glory Days"
+
+ [[albums.songs]]
+ name = "Dancing in the Dark"
+`
+
+ type Song struct {
+ Name string
+ }
+
+ type Album struct {
+ Name string
+ Songs []Song
+ }
+
+ type Music struct {
+ Albums []Album
+ }
+
+ expected := Music{[]Album{
+ {"Born to Run", []Song{{"Jungleland"}, {"Meeting Across the River"}}},
+ {"Born in the USA", []Song{{"Glory Days"}, {"Dancing in the Dark"}}},
+ }}
+ var got Music
+ if _, err := Decode(tomlTableArrays, &got); err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(expected, got) {
+ t.Fatalf("\n%#v\n!=\n%#v\n", expected, got)
+ }
+}
+
+func TestTableNesting(t *testing.T) {
+ for _, tt := range []struct {
+ t string
+ want []string
+ }{
+ {"[a.b.c]", []string{"a", "b", "c"}},
+ {`[a."b.c"]`, []string{"a", "b.c"}},
+ {`[a.'b.c']`, []string{"a", "b.c"}},
+ {`[a.' b ']`, []string{"a", " b "}},
+ {"[ d.e.f ]", []string{"d", "e", "f"}},
+ {"[ g . h . i ]", []string{"g", "h", "i"}},
+ {`[ j . "ʞ" . 'l' ]`, []string{"j", "ʞ", "l"}},
+ } {
+ var m map[string]interface{}
+ if _, err := Decode(tt.t, &m); err != nil {
+ t.Errorf("Decode(%q): got error: %s", tt.t, err)
+ continue
+ }
+ if keys := extractNestedKeys(m); !reflect.DeepEqual(keys, tt.want) {
+ t.Errorf("Decode(%q): got nested keys %#v; want %#v",
+ tt.t, keys, tt.want)
+ }
+ }
+}
+
+func extractNestedKeys(v map[string]interface{}) []string {
+ var result []string
+ for {
+ if len(v) != 1 {
+ return result
+ }
+ for k, m := range v {
+ result = append(result, k)
+ var ok bool
+ v, ok = m.(map[string]interface{})
+ if !ok {
+ return result
+ }
+ }
+
+ }
+}
+
+// Case insensitive matching tests.
+// A bit more comprehensive than needed given the current implementation,
+// but implementations change.
+// Probably still missing demonstrations of some ugly corner cases regarding
+// case insensitive matching and multiple fields.
+func TestCase(t *testing.T) {
+ var caseToml = `
+tOpString = "string"
+tOpInt = 1
+tOpFloat = 1.1
+tOpBool = true
+tOpdate = 2006-01-02T15:04:05Z
+tOparray = [ "array" ]
+Match = "i should be in Match only"
+MatcH = "i should be in MatcH only"
+once = "just once"
+[nEst.eD]
+nEstedString = "another string"
+`
+
+ type InsensitiveEd struct {
+ NestedString string
+ }
+
+ type InsensitiveNest struct {
+ Ed InsensitiveEd
+ }
+
+ type Insensitive struct {
+ TopString string
+ TopInt int
+ TopFloat float64
+ TopBool bool
+ TopDate time.Time
+ TopArray []string
+ Match string
+ MatcH string
+ Once string
+ OncE string
+ Nest InsensitiveNest
+ }
+
+ tme, err := time.Parse(time.RFC3339, time.RFC3339[:len(time.RFC3339)-5])
+ if err != nil {
+ panic(err)
+ }
+ expected := Insensitive{
+ TopString: "string",
+ TopInt: 1,
+ TopFloat: 1.1,
+ TopBool: true,
+ TopDate: tme,
+ TopArray: []string{"array"},
+ MatcH: "i should be in MatcH only",
+ Match: "i should be in Match only",
+ Once: "just once",
+ OncE: "",
+ Nest: InsensitiveNest{
+ Ed: InsensitiveEd{NestedString: "another string"},
+ },
+ }
+ var got Insensitive
+ if _, err := Decode(caseToml, &got); err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(expected, got) {
+ t.Fatalf("\n%#v\n!=\n%#v\n", expected, got)
+ }
+}
+
+func TestPointers(t *testing.T) {
+ type Object struct {
+ Type string
+ Description string
+ }
+
+ type Dict struct {
+ NamedObject map[string]*Object
+ BaseObject *Object
+ Strptr *string
+ Strptrs []*string
+ }
+ s1, s2, s3 := "blah", "abc", "def"
+ expected := &Dict{
+ Strptr: &s1,
+ Strptrs: []*string{&s2, &s3},
+ NamedObject: map[string]*Object{
+ "foo": {"FOO", "fooooo!!!"},
+ "bar": {"BAR", "ba-ba-ba-ba-barrrr!!!"},
+ },
+ BaseObject: &Object{"BASE", "da base"},
+ }
+
+ ex1 := `
+Strptr = "blah"
+Strptrs = ["abc", "def"]
+
+[NamedObject.foo]
+Type = "FOO"
+Description = "fooooo!!!"
+
+[NamedObject.bar]
+Type = "BAR"
+Description = "ba-ba-ba-ba-barrrr!!!"
+
+[BaseObject]
+Type = "BASE"
+Description = "da base"
+`
+ dict := new(Dict)
+ _, err := Decode(ex1, dict)
+ if err != nil {
+ t.Errorf("Decode error: %v", err)
+ }
+ if !reflect.DeepEqual(expected, dict) {
+ t.Fatalf("\n%#v\n!=\n%#v\n", expected, dict)
+ }
+}
+
+func TestDecodeDatetime(t *testing.T) {
+ const noTimestamp = "2006-01-02T15:04:05"
+ for _, tt := range []struct {
+ s string
+ t string
+ format string
+ }{
+ {"1979-05-27T07:32:00Z", "1979-05-27T07:32:00Z", time.RFC3339},
+ {"1979-05-27T00:32:00-07:00", "1979-05-27T00:32:00-07:00", time.RFC3339},
+ {
+ "1979-05-27T00:32:00.999999-07:00",
+ "1979-05-27T00:32:00.999999-07:00",
+ time.RFC3339,
+ },
+ {"1979-05-27T07:32:00", "1979-05-27T07:32:00", noTimestamp},
+ {
+ "1979-05-27T00:32:00.999999",
+ "1979-05-27T00:32:00.999999",
+ noTimestamp,
+ },
+ {"1979-05-27", "1979-05-27T00:00:00", noTimestamp},
+ } {
+ var x struct{ D time.Time }
+ input := "d = " + tt.s
+ if _, err := Decode(input, &x); err != nil {
+ t.Errorf("Decode(%q): got error: %s", input, err)
+ continue
+ }
+ want, err := time.ParseInLocation(tt.format, tt.t, time.Local)
+ if err != nil {
+ panic(err)
+ }
+ if !x.D.Equal(want) {
+ t.Errorf("Decode(%q): got %s; want %s", input, x.D, want)
+ }
+ }
+}
+
+func TestDecodeBadDatetime(t *testing.T) {
+ var x struct{ T time.Time }
+ for _, s := range []string{
+ "123",
+ "2006-01-50T00:00:00Z",
+ "2006-01-30T00:00",
+ "2006-01-30T",
+ } {
+ input := "T = " + s
+ if _, err := Decode(input, &x); err == nil {
+ t.Errorf("Expected invalid DateTime error for %q", s)
+ }
+ }
+}
+
+func TestDecodeMultilineStrings(t *testing.T) {
+ var x struct {
+ S string
+ }
+ const s0 = `s = """
+a b \n c
+d e f
+"""`
+ if _, err := Decode(s0, &x); err != nil {
+ t.Fatal(err)
+ }
+ if want := "a b \n c\nd e f\n"; x.S != want {
+ t.Errorf("got: %q; want: %q", x.S, want)
+ }
+ const s1 = `s = """a b c\
+"""`
+ if _, err := Decode(s1, &x); err != nil {
+ t.Fatal(err)
+ }
+ if want := "a b c"; x.S != want {
+ t.Errorf("got: %q; want: %q", x.S, want)
+ }
+}
+
+type sphere struct {
+ Center [3]float64
+ Radius float64
+}
+
+func TestDecodeSimpleArray(t *testing.T) {
+ var s1 sphere
+ if _, err := Decode(`center = [0.0, 1.5, 0.0]`, &s1); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestDecodeArrayWrongSize(t *testing.T) {
+ var s1 sphere
+ if _, err := Decode(`center = [0.1, 2.3]`, &s1); err == nil {
+ t.Fatal("Expected array type mismatch error")
+ }
+}
+
+func TestDecodeLargeIntoSmallInt(t *testing.T) {
+ type table struct {
+ Value int8
+ }
+ var tab table
+ if _, err := Decode(`value = 500`, &tab); err == nil {
+ t.Fatal("Expected integer out-of-bounds error.")
+ }
+}
+
+func TestDecodeSizedInts(t *testing.T) {
+ type table struct {
+ U8 uint8
+ U16 uint16
+ U32 uint32
+ U64 uint64
+ U uint
+ I8 int8
+ I16 int16
+ I32 int32
+ I64 int64
+ I int
+ }
+ answer := table{1, 1, 1, 1, 1, -1, -1, -1, -1, -1}
+ toml := `
+ u8 = 1
+ u16 = 1
+ u32 = 1
+ u64 = 1
+ u = 1
+ i8 = -1
+ i16 = -1
+ i32 = -1
+ i64 = -1
+ i = -1
+ `
+ var tab table
+ if _, err := Decode(toml, &tab); err != nil {
+ t.Fatal(err.Error())
+ }
+ if answer != tab {
+ t.Fatalf("Expected %#v but got %#v", answer, tab)
+ }
+}
+
+func TestDecodeInts(t *testing.T) {
+ for _, tt := range []struct {
+ s string
+ want int64
+ }{
+ {"0", 0},
+ {"+99", 99},
+ {"-10", -10},
+ {"1_234_567", 1234567},
+ {"1_2_3_4", 1234},
+ {"-9_223_372_036_854_775_808", math.MinInt64},
+ {"9_223_372_036_854_775_807", math.MaxInt64},
+ } {
+ var x struct{ N int64 }
+ input := "n = " + tt.s
+ if _, err := Decode(input, &x); err != nil {
+ t.Errorf("Decode(%q): got error: %s", input, err)
+ continue
+ }
+ if x.N != tt.want {
+ t.Errorf("Decode(%q): got %d; want %d", input, x.N, tt.want)
+ }
+ }
+}
+
+func TestDecodeFloats(t *testing.T) {
+ for _, tt := range []struct {
+ s string
+ want float64
+ }{
+ {"+1.0", 1},
+ {"3.1415", 3.1415},
+ {"-0.01", -0.01},
+ {"5e+22", 5e22},
+ {"1e6", 1e6},
+ {"-2E-2", -2e-2},
+ {"6.626e-34", 6.626e-34},
+ {"9_224_617.445_991_228_313", 9224617.445991228313},
+ {"9_876.54_32e1_0", 9876.5432e10},
+ } {
+ var x struct{ N float64 }
+ input := "n = " + tt.s
+ if _, err := Decode(input, &x); err != nil {
+ t.Errorf("Decode(%q): got error: %s", input, err)
+ continue
+ }
+ if x.N != tt.want {
+ t.Errorf("Decode(%q): got %f; want %f", input, x.N, tt.want)
+ }
+ }
+}
+
+func TestDecodeMalformedNumbers(t *testing.T) {
+ for _, tt := range []struct {
+ s string
+ want string
+ }{
+ {"++99", "expected a digit"},
+ {"0..1", "must be followed by one or more digits"},
+ {"0.1.2", "Invalid float value"},
+ {"1e2.3", "Invalid float value"},
+ {"1e2e3", "Invalid float value"},
+ {"_123", "expected value"},
+ {"123_", "surrounded by digits"},
+ {"1._23", "surrounded by digits"},
+ {"1e__23", "surrounded by digits"},
+ {"123.", "must be followed by one or more digits"},
+ {"1.e2", "must be followed by one or more digits"},
+ } {
+ var x struct{ N interface{} }
+ input := "n = " + tt.s
+ _, err := Decode(input, &x)
+ if err == nil {
+ t.Errorf("Decode(%q): got nil, want error containing %q",
+ input, tt.want)
+ continue
+ }
+ if !strings.Contains(err.Error(), tt.want) {
+ t.Errorf("Decode(%q): got %q, want error containing %q",
+ input, err, tt.want)
+ }
+ }
+}
+
+func TestDecodeBadValues(t *testing.T) {
+ for _, tt := range []struct {
+ v interface{}
+ want string
+ }{
+ {3, "non-pointer int"},
+ {(*int)(nil), "nil"},
+ } {
+ _, err := Decode(`x = 3`, tt.v)
+ if err == nil {
+ t.Errorf("Decode(%v): got nil; want error containing %q",
+ tt.v, tt.want)
+ continue
+ }
+ if !strings.Contains(err.Error(), tt.want) {
+ t.Errorf("Decode(%v): got %q; want error containing %q",
+ tt.v, err, tt.want)
+ }
+ }
+}
+
+func TestUnmarshaler(t *testing.T) {
+
+ var tomlBlob = `
+[dishes.hamboogie]
+name = "Hamboogie with fries"
+price = 10.99
+
+[[dishes.hamboogie.ingredients]]
+name = "Bread Bun"
+
+[[dishes.hamboogie.ingredients]]
+name = "Lettuce"
+
+[[dishes.hamboogie.ingredients]]
+name = "Real Beef Patty"
+
+[[dishes.hamboogie.ingredients]]
+name = "Tomato"
+
+[dishes.eggsalad]
+name = "Egg Salad with rice"
+price = 3.99
+
+[[dishes.eggsalad.ingredients]]
+name = "Egg"
+
+[[dishes.eggsalad.ingredients]]
+name = "Mayo"
+
+[[dishes.eggsalad.ingredients]]
+name = "Rice"
+`
+ m := &menu{}
+ if _, err := Decode(tomlBlob, m); err != nil {
+ t.Fatal(err)
+ }
+
+ if len(m.Dishes) != 2 {
+ t.Log("two dishes should be loaded with UnmarshalTOML()")
+ t.Errorf("expected %d but got %d", 2, len(m.Dishes))
+ }
+
+ eggSalad := m.Dishes["eggsalad"]
+ if _, ok := interface{}(eggSalad).(dish); !ok {
+ t.Errorf("expected a dish")
+ }
+
+ if eggSalad.Name != "Egg Salad with rice" {
+ t.Errorf("expected the dish to be named 'Egg Salad with rice'")
+ }
+
+ if len(eggSalad.Ingredients) != 3 {
+ t.Log("dish should be loaded with UnmarshalTOML()")
+ t.Errorf("expected %d but got %d", 3, len(eggSalad.Ingredients))
+ }
+
+ found := false
+ for _, i := range eggSalad.Ingredients {
+ if i.Name == "Rice" {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Error("Rice was not loaded in UnmarshalTOML()")
+ }
+
+ // test on a value - must be passed as *
+ o := menu{}
+ if _, err := Decode(tomlBlob, &o); err != nil {
+ t.Fatal(err)
+ }
+
+}
+
+func TestDecodeInlineTable(t *testing.T) {
+ input := `
+[CookieJar]
+Types = {Chocolate = "yummy", Oatmeal = "best ever"}
+
+[Seasons]
+Locations = {NY = {Temp = "not cold", Rating = 4}, MI = {Temp = "freezing", Rating = 9}}
+`
+ type cookieJar struct {
+ Types map[string]string
+ }
+ type properties struct {
+ Temp string
+ Rating int
+ }
+ type seasons struct {
+ Locations map[string]properties
+ }
+ type wrapper struct {
+ CookieJar cookieJar
+ Seasons seasons
+ }
+ var got wrapper
+
+ meta, err := Decode(input, &got)
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := wrapper{
+ CookieJar: cookieJar{
+ Types: map[string]string{
+ "Chocolate": "yummy",
+ "Oatmeal": "best ever",
+ },
+ },
+ Seasons: seasons{
+ Locations: map[string]properties{
+ "NY": {
+ Temp: "not cold",
+ Rating: 4,
+ },
+ "MI": {
+ Temp: "freezing",
+ Rating: 9,
+ },
+ },
+ },
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("after decode, got:\n\n%#v\n\nwant:\n\n%#v", got, want)
+ }
+ if len(meta.keys) != 12 {
+ t.Errorf("after decode, got %d meta keys; want 12", len(meta.keys))
+ }
+ if len(meta.types) != 12 {
+ t.Errorf("after decode, got %d meta types; want 12", len(meta.types))
+ }
+}
+
+func TestDecodeInlineTableArray(t *testing.T) {
+ type point struct {
+ X, Y, Z int
+ }
+ var got struct {
+ Points []point
+ }
+ // Example inline table array from the spec.
+ const in = `
+points = [ { x = 1, y = 2, z = 3 },
+ { x = 7, y = 8, z = 9 },
+ { x = 2, y = 4, z = 8 } ]
+
+`
+ if _, err := Decode(in, &got); err != nil {
+ t.Fatal(err)
+ }
+ want := []point{
+ {X: 1, Y: 2, Z: 3},
+ {X: 7, Y: 8, Z: 9},
+ {X: 2, Y: 4, Z: 8},
+ }
+ if !reflect.DeepEqual(got.Points, want) {
+ t.Errorf("got %#v; want %#v", got.Points, want)
+ }
+}
+
+func TestDecodeMalformedInlineTable(t *testing.T) {
+ for _, tt := range []struct {
+ s string
+ want string
+ }{
+ {"{,}", "unexpected comma"},
+ {"{x = 3 y = 4}", "expected a comma or an inline table terminator"},
+ {"{x=3,,y=4}", "unexpected comma"},
+ {"{x=3,\ny=4}", "newlines not allowed"},
+ {"{x=3\n,y=4}", "newlines not allowed"},
+ } {
+ var x struct{ A map[string]int }
+ input := "a = " + tt.s
+ _, err := Decode(input, &x)
+ if err == nil {
+ t.Errorf("Decode(%q): got nil, want error containing %q",
+ input, tt.want)
+ continue
+ }
+ if !strings.Contains(err.Error(), tt.want) {
+ t.Errorf("Decode(%q): got %q, want error containing %q",
+ input, err, tt.want)
+ }
+ }
+}
+
+type menu struct {
+ Dishes map[string]dish
+}
+
+func (m *menu) UnmarshalTOML(p interface{}) error {
+ m.Dishes = make(map[string]dish)
+ data, _ := p.(map[string]interface{})
+ dishes := data["dishes"].(map[string]interface{})
+ for n, v := range dishes {
+ if d, ok := v.(map[string]interface{}); ok {
+ nd := dish{}
+ nd.UnmarshalTOML(d)
+ m.Dishes[n] = nd
+ } else {
+ return fmt.Errorf("not a dish")
+ }
+ }
+ return nil
+}
+
+type dish struct {
+ Name string
+ Price float32
+ Ingredients []ingredient
+}
+
+func (d *dish) UnmarshalTOML(p interface{}) error {
+ data, _ := p.(map[string]interface{})
+ d.Name, _ = data["name"].(string)
+ d.Price, _ = data["price"].(float32)
+ ingredients, _ := data["ingredients"].([]map[string]interface{})
+ for _, e := range ingredients {
+ n, _ := interface{}(e).(map[string]interface{})
+ name, _ := n["name"].(string)
+ i := ingredient{name}
+ d.Ingredients = append(d.Ingredients, i)
+ }
+ return nil
+}
+
+type ingredient struct {
+ Name string
+}
+
+func TestDecodeSlices(t *testing.T) {
+ type T struct {
+ S []string
+ }
+ for i, tt := range []struct {
+ v T
+ input string
+ want T
+ }{
+ {T{}, "", T{}},
+ {T{[]string{}}, "", T{[]string{}}},
+ {T{[]string{"a", "b"}}, "", T{[]string{"a", "b"}}},
+ {T{}, "S = []", T{[]string{}}},
+ {T{[]string{}}, "S = []", T{[]string{}}},
+ {T{[]string{"a", "b"}}, "S = []", T{[]string{}}},
+ {T{}, `S = ["x"]`, T{[]string{"x"}}},
+ {T{[]string{}}, `S = ["x"]`, T{[]string{"x"}}},
+ {T{[]string{"a", "b"}}, `S = ["x"]`, T{[]string{"x"}}},
+ } {
+ if _, err := Decode(tt.input, &tt.v); err != nil {
+ t.Errorf("[%d] %s", i, err)
+ continue
+ }
+ if !reflect.DeepEqual(tt.v, tt.want) {
+ t.Errorf("[%d] got %#v; want %#v", i, tt.v, tt.want)
+ }
+ }
+}
+
+func TestDecodePrimitive(t *testing.T) {
+ type S struct {
+ P Primitive
+ }
+ type T struct {
+ S []int
+ }
+ slicep := func(s []int) *[]int { return &s }
+ arrayp := func(a [2]int) *[2]int { return &a }
+ mapp := func(m map[string]int) *map[string]int { return &m }
+ for i, tt := range []struct {
+ v interface{}
+ input string
+ want interface{}
+ }{
+ // slices
+ {slicep(nil), "", slicep(nil)},
+ {slicep([]int{}), "", slicep([]int{})},
+ {slicep([]int{1, 2, 3}), "", slicep([]int{1, 2, 3})},
+ {slicep(nil), "P = [1,2]", slicep([]int{1, 2})},
+ {slicep([]int{}), "P = [1,2]", slicep([]int{1, 2})},
+ {slicep([]int{1, 2, 3}), "P = [1,2]", slicep([]int{1, 2})},
+
+ // arrays
+ {arrayp([2]int{2, 3}), "", arrayp([2]int{2, 3})},
+ {arrayp([2]int{2, 3}), "P = [3,4]", arrayp([2]int{3, 4})},
+
+ // maps
+ {mapp(nil), "", mapp(nil)},
+ {mapp(map[string]int{}), "", mapp(map[string]int{})},
+ {mapp(map[string]int{"a": 1}), "", mapp(map[string]int{"a": 1})},
+ {mapp(nil), "[P]\na = 2", mapp(map[string]int{"a": 2})},
+ {mapp(map[string]int{}), "[P]\na = 2", mapp(map[string]int{"a": 2})},
+ {mapp(map[string]int{"a": 1, "b": 3}), "[P]\na = 2", mapp(map[string]int{"a": 2, "b": 3})},
+
+ // structs
+ {&T{nil}, "[P]", &T{nil}},
+ {&T{[]int{}}, "[P]", &T{[]int{}}},
+ {&T{[]int{1, 2, 3}}, "[P]", &T{[]int{1, 2, 3}}},
+ {&T{nil}, "[P]\nS = [1,2]", &T{[]int{1, 2}}},
+ {&T{[]int{}}, "[P]\nS = [1,2]", &T{[]int{1, 2}}},
+ {&T{[]int{1, 2, 3}}, "[P]\nS = [1,2]", &T{[]int{1, 2}}},
+ } {
+ var s S
+ md, err := Decode(tt.input, &s)
+ if err != nil {
+ t.Errorf("[%d] Decode error: %s", i, err)
+ continue
+ }
+ if err := md.PrimitiveDecode(s.P, tt.v); err != nil {
+ t.Errorf("[%d] PrimitiveDecode error: %s", i, err)
+ continue
+ }
+ if !reflect.DeepEqual(tt.v, tt.want) {
+ t.Errorf("[%d] got %#v; want %#v", i, tt.v, tt.want)
+ }
+ }
+}
+
+func TestDecodeErrors(t *testing.T) {
+ for _, s := range []string{
+ `x="`,
+ `x='`,
+ `x='''`,
+
+ // Cases found by fuzzing in
+ // https://github.com/BurntSushi/toml/issues/155.
+ `""�`, // used to panic with index out of range
+ `e="""`, // used to hang
+ } {
+ var x struct{}
+ _, err := Decode(s, &x)
+ if err == nil {
+ t.Errorf("Decode(%q): got nil error", s)
+ }
+ }
+}
+
+// Test for https://github.com/BurntSushi/toml/pull/166.
+func TestDecodeBoolArray(t *testing.T) {
+ for _, tt := range []struct {
+ s string
+ got interface{}
+ want interface{}
+ }{
+ {
+ "a = [true, false]",
+ &struct{ A []bool }{},
+ &struct{ A []bool }{[]bool{true, false}},
+ },
+ {
+ "a = {a = true, b = false}",
+ &struct{ A map[string]bool }{},
+ &struct{ A map[string]bool }{map[string]bool{"a": true, "b": false}},
+ },
+ } {
+ if _, err := Decode(tt.s, tt.got); err != nil {
+ t.Errorf("Decode(%q): %s", tt.s, err)
+ continue
+ }
+ if !reflect.DeepEqual(tt.got, tt.want) {
+ t.Errorf("Decode(%q): got %#v; want %#v", tt.s, tt.got, tt.want)
+ }
+ }
+}
+
+func ExampleMetaData_PrimitiveDecode() {
+ var md MetaData
+ var err error
+
+ var tomlBlob = `
+ranking = ["Springsteen", "J Geils"]
+
+[bands.Springsteen]
+started = 1973
+albums = ["Greetings", "WIESS", "Born to Run", "Darkness"]
+
+[bands."J Geils"]
+started = 1970
+albums = ["The J. Geils Band", "Full House", "Blow Your Face Out"]
+`
+
+ type band struct {
+ Started int
+ Albums []string
+ }
+ type classics struct {
+ Ranking []string
+ Bands map[string]Primitive
+ }
+
+ // Do the initial decode. Reflection is delayed on Primitive values.
+ var music classics
+ if md, err = Decode(tomlBlob, &music); err != nil {
+ log.Fatal(err)
+ }
+
+ // MetaData still includes information on Primitive values.
+ fmt.Printf("Is `bands.Springsteen` defined? %v\n",
+ md.IsDefined("bands", "Springsteen"))
+
+ // Decode primitive data into Go values.
+ for _, artist := range music.Ranking {
+ // A band is a primitive value, so we need to decode it to get a
+ // real `band` value.
+ primValue := music.Bands[artist]
+
+ var aBand band
+ if err = md.PrimitiveDecode(primValue, &aBand); err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("%s started in %d.\n", artist, aBand.Started)
+ }
+ // Check to see if there were any fields left undecoded.
+ // Note that this won't be empty before decoding the Primitive value!
+ fmt.Printf("Undecoded: %q\n", md.Undecoded())
+
+ // Output:
+ // Is `bands.Springsteen` defined? true
+ // Springsteen started in 1973.
+ // J Geils started in 1970.
+ // Undecoded: []
+}
+
+func ExampleDecode() {
+ var tomlBlob = `
+# Some comments.
+[alpha]
+ip = "10.0.0.1"
+
+ [alpha.config]
+ Ports = [ 8001, 8002 ]
+ Location = "Toronto"
+ Created = 1987-07-05T05:45:00Z
+
+[beta]
+ip = "10.0.0.2"
+
+ [beta.config]
+ Ports = [ 9001, 9002 ]
+ Location = "New Jersey"
+ Created = 1887-01-05T05:55:00Z
+`
+
+ type serverConfig struct {
+ Ports []int
+ Location string
+ Created time.Time
+ }
+
+ type server struct {
+ IP string `toml:"ip,omitempty"`
+ Config serverConfig `toml:"config"`
+ }
+
+ type servers map[string]server
+
+ var config servers
+ if _, err := Decode(tomlBlob, &config); err != nil {
+ log.Fatal(err)
+ }
+
+ for _, name := range []string{"alpha", "beta"} {
+ s := config[name]
+ fmt.Printf("Server: %s (ip: %s) in %s created on %s\n",
+ name, s.IP, s.Config.Location,
+ s.Config.Created.Format("2006-01-02"))
+ fmt.Printf("Ports: %v\n", s.Config.Ports)
+ }
+
+ // Output:
+ // Server: alpha (ip: 10.0.0.1) in Toronto created on 1987-07-05
+ // Ports: [8001 8002]
+ // Server: beta (ip: 10.0.0.2) in New Jersey created on 1887-01-05
+ // Ports: [9001 9002]
+}
+
+type duration struct {
+ time.Duration
+}
+
+func (d *duration) UnmarshalText(text []byte) error {
+ var err error
+ d.Duration, err = time.ParseDuration(string(text))
+ return err
+}
+
+// Example Unmarshaler shows how to decode TOML strings into your own
+// custom data type.
+func Example_unmarshaler() {
+ blob := `
+[[song]]
+name = "Thunder Road"
+duration = "4m49s"
+
+[[song]]
+name = "Stairway to Heaven"
+duration = "8m03s"
+`
+ type song struct {
+ Name string
+ Duration duration
+ }
+ type songs struct {
+ Song []song
+ }
+ var favorites songs
+ if _, err := Decode(blob, &favorites); err != nil {
+ log.Fatal(err)
+ }
+
+ // Code to implement the TextUnmarshaler interface for `duration`:
+ //
+ // type duration struct {
+ // time.Duration
+ // }
+ //
+ // func (d *duration) UnmarshalText(text []byte) error {
+ // var err error
+ // d.Duration, err = time.ParseDuration(string(text))
+ // return err
+ // }
+
+ for _, s := range favorites.Song {
+ fmt.Printf("%s (%s)\n", s.Name, s.Duration)
+ }
+ // Output:
+ // Thunder Road (4m49s)
+ // Stairway to Heaven (8m3s)
+}
+
+// Example StrictDecoding shows how to detect whether there are keys in the
+// TOML document that weren't decoded into the value given. This is useful
+// for returning an error to the user if they've included extraneous fields
+// in their configuration.
+func Example_strictDecoding() {
+ var blob = `
+key1 = "value1"
+key2 = "value2"
+key3 = "value3"
+`
+ type config struct {
+ Key1 string
+ Key3 string
+ }
+
+ var conf config
+ md, err := Decode(blob, &conf)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("Undecoded keys: %q\n", md.Undecoded())
+ // Output:
+ // Undecoded keys: ["key2"]
+}
+
+// Example UnmarshalTOML shows how to implement a struct type that knows how to
+// unmarshal itself. The struct must take full responsibility for mapping the
+// values passed into the struct. The method may be used with interfaces in a
+// struct in cases where the actual type is not known until the data is
+// examined.
+func Example_unmarshalTOML() {
+
+ var blob = `
+[[parts]]
+type = "valve"
+id = "valve-1"
+size = 1.2
+rating = 4
+
+[[parts]]
+type = "valve"
+id = "valve-2"
+size = 2.1
+rating = 5
+
+[[parts]]
+type = "pipe"
+id = "pipe-1"
+length = 2.1
+diameter = 12
+
+[[parts]]
+type = "cable"
+id = "cable-1"
+length = 12
+rating = 3.1
+`
+ o := &order{}
+ err := Unmarshal([]byte(blob), o)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println(len(o.parts))
+
+ for _, part := range o.parts {
+ fmt.Println(part.Name())
+ }
+
+ // Code to implement UmarshalJSON.
+
+ // type order struct {
+ // // NOTE `order.parts` is a private slice of type `part` which is an
+ // // interface and may only be loaded from toml using the
+ // // UnmarshalTOML() method of the Umarshaler interface.
+ // parts parts
+ // }
+
+ // func (o *order) UnmarshalTOML(data interface{}) error {
+
+ // // NOTE the example below contains detailed type casting to show how
+ // // the 'data' is retrieved. In operational use, a type cast wrapper
+ // // may be preferred e.g.
+ // //
+ // // func AsMap(v interface{}) (map[string]interface{}, error) {
+ // // return v.(map[string]interface{})
+ // // }
+ // //
+ // // resulting in:
+ // // d, _ := AsMap(data)
+ // //
+
+ // d, _ := data.(map[string]interface{})
+ // parts, _ := d["parts"].([]map[string]interface{})
+
+ // for _, p := range parts {
+
+ // typ, _ := p["type"].(string)
+ // id, _ := p["id"].(string)
+
+ // // detect the type of part and handle each case
+ // switch p["type"] {
+ // case "valve":
+
+ // size := float32(p["size"].(float64))
+ // rating := int(p["rating"].(int64))
+
+ // valve := &valve{
+ // Type: typ,
+ // ID: id,
+ // Size: size,
+ // Rating: rating,
+ // }
+
+ // o.parts = append(o.parts, valve)
+
+ // case "pipe":
+
+ // length := float32(p["length"].(float64))
+ // diameter := int(p["diameter"].(int64))
+
+ // pipe := &pipe{
+ // Type: typ,
+ // ID: id,
+ // Length: length,
+ // Diameter: diameter,
+ // }
+
+ // o.parts = append(o.parts, pipe)
+
+ // case "cable":
+
+ // length := int(p["length"].(int64))
+ // rating := float32(p["rating"].(float64))
+
+ // cable := &cable{
+ // Type: typ,
+ // ID: id,
+ // Length: length,
+ // Rating: rating,
+ // }
+
+ // o.parts = append(o.parts, cable)
+
+ // }
+ // }
+
+ // return nil
+ // }
+
+ // type parts []part
+
+ // type part interface {
+ // Name() string
+ // }
+
+ // type valve struct {
+ // Type string
+ // ID string
+ // Size float32
+ // Rating int
+ // }
+
+ // func (v *valve) Name() string {
+ // return fmt.Sprintf("VALVE: %s", v.ID)
+ // }
+
+ // type pipe struct {
+ // Type string
+ // ID string
+ // Length float32
+ // Diameter int
+ // }
+
+ // func (p *pipe) Name() string {
+ // return fmt.Sprintf("PIPE: %s", p.ID)
+ // }
+
+ // type cable struct {
+ // Type string
+ // ID string
+ // Length int
+ // Rating float32
+ // }
+
+ // func (c *cable) Name() string {
+ // return fmt.Sprintf("CABLE: %s", c.ID)
+ // }
+
+ // Output:
+ // 4
+ // VALVE: valve-1
+ // VALVE: valve-2
+ // PIPE: pipe-1
+ // CABLE: cable-1
+
+}
+
+type order struct {
+ // NOTE `order.parts` is a private slice of type `part` which is an
+ // interface and may only be loaded from toml using the UnmarshalTOML()
+ // method of the Umarshaler interface.
+ parts parts
+}
+
+func (o *order) UnmarshalTOML(data interface{}) error {
+
+ // NOTE the example below contains detailed type casting to show how
+ // the 'data' is retrieved. In operational use, a type cast wrapper
+ // may be preferred e.g.
+ //
+ // func AsMap(v interface{}) (map[string]interface{}, error) {
+ // return v.(map[string]interface{})
+ // }
+ //
+ // resulting in:
+ // d, _ := AsMap(data)
+ //
+
+ d, _ := data.(map[string]interface{})
+ parts, _ := d["parts"].([]map[string]interface{})
+
+ for _, p := range parts {
+
+ typ, _ := p["type"].(string)
+ id, _ := p["id"].(string)
+
+ // detect the type of part and handle each case
+ switch p["type"] {
+ case "valve":
+
+ size := float32(p["size"].(float64))
+ rating := int(p["rating"].(int64))
+
+ valve := &valve{
+ Type: typ,
+ ID: id,
+ Size: size,
+ Rating: rating,
+ }
+
+ o.parts = append(o.parts, valve)
+
+ case "pipe":
+
+ length := float32(p["length"].(float64))
+ diameter := int(p["diameter"].(int64))
+
+ pipe := &pipe{
+ Type: typ,
+ ID: id,
+ Length: length,
+ Diameter: diameter,
+ }
+
+ o.parts = append(o.parts, pipe)
+
+ case "cable":
+
+ length := int(p["length"].(int64))
+ rating := float32(p["rating"].(float64))
+
+ cable := &cable{
+ Type: typ,
+ ID: id,
+ Length: length,
+ Rating: rating,
+ }
+
+ o.parts = append(o.parts, cable)
+
+ }
+ }
+
+ return nil
+}
+
+type parts []part
+
+type part interface {
+ Name() string
+}
+
+type valve struct {
+ Type string
+ ID string
+ Size float32
+ Rating int
+}
+
+func (v *valve) Name() string {
+ return fmt.Sprintf("VALVE: %s", v.ID)
+}
+
+type pipe struct {
+ Type string
+ ID string
+ Length float32
+ Diameter int
+}
+
+func (p *pipe) Name() string {
+ return fmt.Sprintf("PIPE: %s", p.ID)
+}
+
+type cable struct {
+ Type string
+ ID string
+ Length int
+ Rating float32
+}
+
+func (c *cable) Name() string {
+ return fmt.Sprintf("CABLE: %s", c.ID)
+}
diff --git a/vendor/github.com/BurntSushi/toml/doc.go b/vendor/github.com/BurntSushi/toml/doc.go
new file mode 100644
index 0000000..b371f39
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/doc.go
@@ -0,0 +1,27 @@
+/*
+Package toml provides facilities for decoding and encoding TOML configuration
+files via reflection. There is also support for delaying decoding with
+the Primitive type, and querying the set of keys in a TOML document with the
+MetaData type.
+
+The specification implemented: https://github.com/toml-lang/toml
+
+The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
+whether a file is a valid TOML document. It can also be used to print the
+type of each key in a TOML document.
+
+Testing
+
+There are two important types of tests used for this package. The first is
+contained inside '*_test.go' files and uses the standard Go unit testing
+framework. These tests are primarily devoted to holistically testing the
+decoder and encoder.
+
+The second type of testing is used to verify the implementation's adherence
+to the TOML specification. These tests have been factored into their own
+project: https://github.com/BurntSushi/toml-test
+
+The reason the tests are in a separate project is so that they can be used by
+any implementation of TOML. Namely, it is language agnostic.
+*/
+package toml
diff --git a/vendor/github.com/BurntSushi/toml/encode.go b/vendor/github.com/BurntSushi/toml/encode.go
new file mode 100644
index 0000000..d905c21
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/encode.go
@@ -0,0 +1,568 @@
+package toml
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "reflect"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+)
+
+type tomlEncodeError struct{ error }
+
+var (
+ errArrayMixedElementTypes = errors.New(
+ "toml: cannot encode array with mixed element types")
+ errArrayNilElement = errors.New(
+ "toml: cannot encode array with nil element")
+ errNonString = errors.New(
+ "toml: cannot encode a map with non-string key type")
+ errAnonNonStruct = errors.New(
+ "toml: cannot encode an anonymous field that is not a struct")
+ errArrayNoTable = errors.New(
+ "toml: TOML array element cannot contain a table")
+ errNoKey = errors.New(
+ "toml: top-level values must be Go maps or structs")
+ errAnything = errors.New("") // used in testing
+)
+
+var quotedReplacer = strings.NewReplacer(
+ "\t", "\\t",
+ "\n", "\\n",
+ "\r", "\\r",
+ "\"", "\\\"",
+ "\\", "\\\\",
+)
+
+// Encoder controls the encoding of Go values to a TOML document to some
+// io.Writer.
+//
+// The indentation level can be controlled with the Indent field.
+type Encoder struct {
+ // A single indentation level. By default it is two spaces.
+ Indent string
+
+ // hasWritten is whether we have written any output to w yet.
+ hasWritten bool
+ w *bufio.Writer
+}
+
+// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
+// given. By default, a single indentation level is 2 spaces.
+func NewEncoder(w io.Writer) *Encoder {
+ return &Encoder{
+ w: bufio.NewWriter(w),
+ Indent: " ",
+ }
+}
+
+// Encode writes a TOML representation of the Go value to the underlying
+// io.Writer. If the value given cannot be encoded to a valid TOML document,
+// then an error is returned.
+//
+// The mapping between Go values and TOML values should be precisely the same
+// as for the Decode* functions. Similarly, the TextMarshaler interface is
+// supported by encoding the resulting bytes as strings. (If you want to write
+// arbitrary binary data then you will need to use something like base64 since
+// TOML does not have any binary types.)
+//
+// When encoding TOML hashes (i.e., Go maps or structs), keys without any
+// sub-hashes are encoded first.
+//
+// If a Go map is encoded, then its keys are sorted alphabetically for
+// deterministic output. More control over this behavior may be provided if
+// there is demand for it.
+//
+// Encoding Go values without a corresponding TOML representation---like map
+// types with non-string keys---will cause an error to be returned. Similarly
+// for mixed arrays/slices, arrays/slices with nil elements, embedded
+// non-struct types and nested slices containing maps or structs.
+// (e.g., [][]map[string]string is not allowed but []map[string]string is OK
+// and so is []map[string][]string.)
+func (enc *Encoder) Encode(v interface{}) error {
+ rv := eindirect(reflect.ValueOf(v))
+ if err := enc.safeEncode(Key([]string{}), rv); err != nil {
+ return err
+ }
+ return enc.w.Flush()
+}
+
+func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ if terr, ok := r.(tomlEncodeError); ok {
+ err = terr.error
+ return
+ }
+ panic(r)
+ }
+ }()
+ enc.encode(key, rv)
+ return nil
+}
+
+func (enc *Encoder) encode(key Key, rv reflect.Value) {
+ // Special case. Time needs to be in ISO8601 format.
+ // Special case. If we can marshal the type to text, then we used that.
+ // Basically, this prevents the encoder for handling these types as
+ // generic structs (or whatever the underlying type of a TextMarshaler is).
+ switch rv.Interface().(type) {
+ case time.Time, TextMarshaler:
+ enc.keyEqElement(key, rv)
+ return
+ }
+
+ k := rv.Kind()
+ switch k {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
+ reflect.Int64,
+ reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
+ reflect.Uint64,
+ reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
+ enc.keyEqElement(key, rv)
+ case reflect.Array, reflect.Slice:
+ if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
+ enc.eArrayOfTables(key, rv)
+ } else {
+ enc.keyEqElement(key, rv)
+ }
+ case reflect.Interface:
+ if rv.IsNil() {
+ return
+ }
+ enc.encode(key, rv.Elem())
+ case reflect.Map:
+ if rv.IsNil() {
+ return
+ }
+ enc.eTable(key, rv)
+ case reflect.Ptr:
+ if rv.IsNil() {
+ return
+ }
+ enc.encode(key, rv.Elem())
+ case reflect.Struct:
+ enc.eTable(key, rv)
+ default:
+ panic(e("unsupported type for key '%s': %s", key, k))
+ }
+}
+
+// eElement encodes any value that can be an array element (primitives and
+// arrays).
+func (enc *Encoder) eElement(rv reflect.Value) {
+ switch v := rv.Interface().(type) {
+ case time.Time:
+ // Special case time.Time as a primitive. Has to come before
+ // TextMarshaler below because time.Time implements
+ // encoding.TextMarshaler, but we need to always use UTC.
+ enc.wf(v.UTC().Format("2006-01-02T15:04:05Z"))
+ return
+ case TextMarshaler:
+ // Special case. Use text marshaler if it's available for this value.
+ if s, err := v.MarshalText(); err != nil {
+ encPanic(err)
+ } else {
+ enc.writeQuoted(string(s))
+ }
+ return
+ }
+ switch rv.Kind() {
+ case reflect.Bool:
+ enc.wf(strconv.FormatBool(rv.Bool()))
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
+ reflect.Int64:
+ enc.wf(strconv.FormatInt(rv.Int(), 10))
+ case reflect.Uint, reflect.Uint8, reflect.Uint16,
+ reflect.Uint32, reflect.Uint64:
+ enc.wf(strconv.FormatUint(rv.Uint(), 10))
+ case reflect.Float32:
+ enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32)))
+ case reflect.Float64:
+ enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64)))
+ case reflect.Array, reflect.Slice:
+ enc.eArrayOrSliceElement(rv)
+ case reflect.Interface:
+ enc.eElement(rv.Elem())
+ case reflect.String:
+ enc.writeQuoted(rv.String())
+ default:
+ panic(e("unexpected primitive type: %s", rv.Kind()))
+ }
+}
+
+// By the TOML spec, all floats must have a decimal with at least one
+// number on either side.
+func floatAddDecimal(fstr string) string {
+ if !strings.Contains(fstr, ".") {
+ return fstr + ".0"
+ }
+ return fstr
+}
+
+func (enc *Encoder) writeQuoted(s string) {
+ enc.wf("\"%s\"", quotedReplacer.Replace(s))
+}
+
+func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
+ length := rv.Len()
+ enc.wf("[")
+ for i := 0; i < length; i++ {
+ elem := rv.Index(i)
+ enc.eElement(elem)
+ if i != length-1 {
+ enc.wf(", ")
+ }
+ }
+ enc.wf("]")
+}
+
+func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
+ if len(key) == 0 {
+ encPanic(errNoKey)
+ }
+ for i := 0; i < rv.Len(); i++ {
+ trv := rv.Index(i)
+ if isNil(trv) {
+ continue
+ }
+ panicIfInvalidKey(key)
+ enc.newline()
+ enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
+ enc.newline()
+ enc.eMapOrStruct(key, trv)
+ }
+}
+
+func (enc *Encoder) eTable(key Key, rv reflect.Value) {
+ panicIfInvalidKey(key)
+ if len(key) == 1 {
+ // Output an extra newline between top-level tables.
+ // (The newline isn't written if nothing else has been written though.)
+ enc.newline()
+ }
+ if len(key) > 0 {
+ enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
+ enc.newline()
+ }
+ enc.eMapOrStruct(key, rv)
+}
+
+func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) {
+ switch rv := eindirect(rv); rv.Kind() {
+ case reflect.Map:
+ enc.eMap(key, rv)
+ case reflect.Struct:
+ enc.eStruct(key, rv)
+ default:
+ panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
+ }
+}
+
+func (enc *Encoder) eMap(key Key, rv reflect.Value) {
+ rt := rv.Type()
+ if rt.Key().Kind() != reflect.String {
+ encPanic(errNonString)
+ }
+
+ // Sort keys so that we have deterministic output. And write keys directly
+ // underneath this key first, before writing sub-structs or sub-maps.
+ var mapKeysDirect, mapKeysSub []string
+ for _, mapKey := range rv.MapKeys() {
+ k := mapKey.String()
+ if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
+ mapKeysSub = append(mapKeysSub, k)
+ } else {
+ mapKeysDirect = append(mapKeysDirect, k)
+ }
+ }
+
+ var writeMapKeys = func(mapKeys []string) {
+ sort.Strings(mapKeys)
+ for _, mapKey := range mapKeys {
+ mrv := rv.MapIndex(reflect.ValueOf(mapKey))
+ if isNil(mrv) {
+ // Don't write anything for nil fields.
+ continue
+ }
+ enc.encode(key.add(mapKey), mrv)
+ }
+ }
+ writeMapKeys(mapKeysDirect)
+ writeMapKeys(mapKeysSub)
+}
+
+func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
+ // Write keys for fields directly under this key first, because if we write
+ // a field that creates a new table, then all keys under it will be in that
+ // table (not the one we're writing here).
+ rt := rv.Type()
+ var fieldsDirect, fieldsSub [][]int
+ var addFields func(rt reflect.Type, rv reflect.Value, start []int)
+ addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
+ for i := 0; i < rt.NumField(); i++ {
+ f := rt.Field(i)
+ // skip unexported fields
+ if f.PkgPath != "" && !f.Anonymous {
+ continue
+ }
+ frv := rv.Field(i)
+ if f.Anonymous {
+ t := f.Type
+ switch t.Kind() {
+ case reflect.Struct:
+ // Treat anonymous struct fields with
+ // tag names as though they are not
+ // anonymous, like encoding/json does.
+ if getOptions(f.Tag).name == "" {
+ addFields(t, frv, f.Index)
+ continue
+ }
+ case reflect.Ptr:
+ if t.Elem().Kind() == reflect.Struct &&
+ getOptions(f.Tag).name == "" {
+ if !frv.IsNil() {
+ addFields(t.Elem(), frv.Elem(), f.Index)
+ }
+ continue
+ }
+ // Fall through to the normal field encoding logic below
+ // for non-struct anonymous fields.
+ }
+ }
+
+ if typeIsHash(tomlTypeOfGo(frv)) {
+ fieldsSub = append(fieldsSub, append(start, f.Index...))
+ } else {
+ fieldsDirect = append(fieldsDirect, append(start, f.Index...))
+ }
+ }
+ }
+ addFields(rt, rv, nil)
+
+ var writeFields = func(fields [][]int) {
+ for _, fieldIndex := range fields {
+ sft := rt.FieldByIndex(fieldIndex)
+ sf := rv.FieldByIndex(fieldIndex)
+ if isNil(sf) {
+ // Don't write anything for nil fields.
+ continue
+ }
+
+ opts := getOptions(sft.Tag)
+ if opts.skip {
+ continue
+ }
+ keyName := sft.Name
+ if opts.name != "" {
+ keyName = opts.name
+ }
+ if opts.omitempty && isEmpty(sf) {
+ continue
+ }
+ if opts.omitzero && isZero(sf) {
+ continue
+ }
+
+ enc.encode(key.add(keyName), sf)
+ }
+ }
+ writeFields(fieldsDirect)
+ writeFields(fieldsSub)
+}
+
+// tomlTypeName returns the TOML type name of the Go value's type. It is
+// used to determine whether the types of array elements are mixed (which is
+// forbidden). If the Go value is nil, then it is illegal for it to be an array
+// element, and valueIsNil is returned as true.
+
+// Returns the TOML type of a Go value. The type may be `nil`, which means
+// no concrete TOML type could be found.
+func tomlTypeOfGo(rv reflect.Value) tomlType {
+ if isNil(rv) || !rv.IsValid() {
+ return nil
+ }
+ switch rv.Kind() {
+ case reflect.Bool:
+ return tomlBool
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
+ reflect.Int64,
+ reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
+ reflect.Uint64:
+ return tomlInteger
+ case reflect.Float32, reflect.Float64:
+ return tomlFloat
+ case reflect.Array, reflect.Slice:
+ if typeEqual(tomlHash, tomlArrayType(rv)) {
+ return tomlArrayHash
+ }
+ return tomlArray
+ case reflect.Ptr, reflect.Interface:
+ return tomlTypeOfGo(rv.Elem())
+ case reflect.String:
+ return tomlString
+ case reflect.Map:
+ return tomlHash
+ case reflect.Struct:
+ switch rv.Interface().(type) {
+ case time.Time:
+ return tomlDatetime
+ case TextMarshaler:
+ return tomlString
+ default:
+ return tomlHash
+ }
+ default:
+ panic("unexpected reflect.Kind: " + rv.Kind().String())
+ }
+}
+
+// tomlArrayType returns the element type of a TOML array. The type returned
+// may be nil if it cannot be determined (e.g., a nil slice or a zero length
+// slize). This function may also panic if it finds a type that cannot be
+// expressed in TOML (such as nil elements, heterogeneous arrays or directly
+// nested arrays of tables).
+func tomlArrayType(rv reflect.Value) tomlType {
+ if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
+ return nil
+ }
+ firstType := tomlTypeOfGo(rv.Index(0))
+ if firstType == nil {
+ encPanic(errArrayNilElement)
+ }
+
+ rvlen := rv.Len()
+ for i := 1; i < rvlen; i++ {
+ elem := rv.Index(i)
+ switch elemType := tomlTypeOfGo(elem); {
+ case elemType == nil:
+ encPanic(errArrayNilElement)
+ case !typeEqual(firstType, elemType):
+ encPanic(errArrayMixedElementTypes)
+ }
+ }
+ // If we have a nested array, then we must make sure that the nested
+ // array contains ONLY primitives.
+ // This checks arbitrarily nested arrays.
+ if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) {
+ nest := tomlArrayType(eindirect(rv.Index(0)))
+ if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) {
+ encPanic(errArrayNoTable)
+ }
+ }
+ return firstType
+}
+
+type tagOptions struct {
+ skip bool // "-"
+ name string
+ omitempty bool
+ omitzero bool
+}
+
+func getOptions(tag reflect.StructTag) tagOptions {
+ t := tag.Get("toml")
+ if t == "-" {
+ return tagOptions{skip: true}
+ }
+ var opts tagOptions
+ parts := strings.Split(t, ",")
+ opts.name = parts[0]
+ for _, s := range parts[1:] {
+ switch s {
+ case "omitempty":
+ opts.omitempty = true
+ case "omitzero":
+ opts.omitzero = true
+ }
+ }
+ return opts
+}
+
+func isZero(rv reflect.Value) bool {
+ switch rv.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return rv.Int() == 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ return rv.Uint() == 0
+ case reflect.Float32, reflect.Float64:
+ return rv.Float() == 0.0
+ }
+ return false
+}
+
+func isEmpty(rv reflect.Value) bool {
+ switch rv.Kind() {
+ case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
+ return rv.Len() == 0
+ case reflect.Bool:
+ return !rv.Bool()
+ }
+ return false
+}
+
+func (enc *Encoder) newline() {
+ if enc.hasWritten {
+ enc.wf("\n")
+ }
+}
+
+func (enc *Encoder) keyEqElement(key Key, val reflect.Value) {
+ if len(key) == 0 {
+ encPanic(errNoKey)
+ }
+ panicIfInvalidKey(key)
+ enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
+ enc.eElement(val)
+ enc.newline()
+}
+
+func (enc *Encoder) wf(format string, v ...interface{}) {
+ if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
+ encPanic(err)
+ }
+ enc.hasWritten = true
+}
+
+func (enc *Encoder) indentStr(key Key) string {
+ return strings.Repeat(enc.Indent, len(key)-1)
+}
+
+func encPanic(err error) {
+ panic(tomlEncodeError{err})
+}
+
+func eindirect(v reflect.Value) reflect.Value {
+ switch v.Kind() {
+ case reflect.Ptr, reflect.Interface:
+ return eindirect(v.Elem())
+ default:
+ return v
+ }
+}
+
+func isNil(rv reflect.Value) bool {
+ switch rv.Kind() {
+ case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
+ return rv.IsNil()
+ default:
+ return false
+ }
+}
+
+func panicIfInvalidKey(key Key) {
+ for _, k := range key {
+ if len(k) == 0 {
+ encPanic(e("Key '%s' is not a valid table name. Key names "+
+ "cannot be empty.", key.maybeQuotedAll()))
+ }
+ }
+}
+
+func isValidKeyName(s string) bool {
+ return len(s) != 0
+}
diff --git a/vendor/github.com/BurntSushi/toml/encode_test.go b/vendor/github.com/BurntSushi/toml/encode_test.go
new file mode 100644
index 0000000..673b7b0
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/encode_test.go
@@ -0,0 +1,615 @@
+package toml
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+ "net"
+ "testing"
+ "time"
+)
+
+func TestEncodeRoundTrip(t *testing.T) {
+ type Config struct {
+ Age int
+ Cats []string
+ Pi float64
+ Perfection []int
+ DOB time.Time
+ Ipaddress net.IP
+ }
+
+ var inputs = Config{
+ 13,
+ []string{"one", "two", "three"},
+ 3.145,
+ []int{11, 2, 3, 4},
+ time.Now(),
+ net.ParseIP("192.168.59.254"),
+ }
+
+ var firstBuffer bytes.Buffer
+ e := NewEncoder(&firstBuffer)
+ err := e.Encode(inputs)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var outputs Config
+ if _, err := Decode(firstBuffer.String(), &outputs); err != nil {
+ t.Logf("Could not decode:\n-----\n%s\n-----\n",
+ firstBuffer.String())
+ t.Fatal(err)
+ }
+
+ // could test each value individually, but I'm lazy
+ var secondBuffer bytes.Buffer
+ e2 := NewEncoder(&secondBuffer)
+ err = e2.Encode(outputs)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if firstBuffer.String() != secondBuffer.String() {
+ t.Error(
+ firstBuffer.String(),
+ "\n\n is not identical to\n\n",
+ secondBuffer.String())
+ }
+}
+
+// XXX(burntsushi)
+// I think these tests probably should be removed. They are good, but they
+// ought to be obsolete by toml-test.
+func TestEncode(t *testing.T) {
+ type Embedded struct {
+ Int int `toml:"_int"`
+ }
+ type NonStruct int
+
+ date := time.Date(2014, 5, 11, 20, 30, 40, 0, time.FixedZone("IST", 3600))
+ dateStr := "2014-05-11T19:30:40Z"
+
+ tests := map[string]struct {
+ input interface{}
+ wantOutput string
+ wantError error
+ }{
+ "bool field": {
+ input: struct {
+ BoolTrue bool
+ BoolFalse bool
+ }{true, false},
+ wantOutput: "BoolTrue = true\nBoolFalse = false\n",
+ },
+ "int fields": {
+ input: struct {
+ Int int
+ Int8 int8
+ Int16 int16
+ Int32 int32
+ Int64 int64
+ }{1, 2, 3, 4, 5},
+ wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n",
+ },
+ "uint fields": {
+ input: struct {
+ Uint uint
+ Uint8 uint8
+ Uint16 uint16
+ Uint32 uint32
+ Uint64 uint64
+ }{1, 2, 3, 4, 5},
+ wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" +
+ "\nUint64 = 5\n",
+ },
+ "float fields": {
+ input: struct {
+ Float32 float32
+ Float64 float64
+ }{1.5, 2.5},
+ wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n",
+ },
+ "string field": {
+ input: struct{ String string }{"foo"},
+ wantOutput: "String = \"foo\"\n",
+ },
+ "string field and unexported field": {
+ input: struct {
+ String string
+ unexported int
+ }{"foo", 0},
+ wantOutput: "String = \"foo\"\n",
+ },
+ "datetime field in UTC": {
+ input: struct{ Date time.Time }{date},
+ wantOutput: fmt.Sprintf("Date = %s\n", dateStr),
+ },
+ "datetime field as primitive": {
+ // Using a map here to fail if isStructOrMap() returns true for
+ // time.Time.
+ input: map[string]interface{}{
+ "Date": date,
+ "Int": 1,
+ },
+ wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr),
+ },
+ "array fields": {
+ input: struct {
+ IntArray0 [0]int
+ IntArray3 [3]int
+ }{[0]int{}, [3]int{1, 2, 3}},
+ wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n",
+ },
+ "slice fields": {
+ input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{
+ nil, []int{}, []int{1, 2, 3},
+ },
+ wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n",
+ },
+ "datetime slices": {
+ input: struct{ DatetimeSlice []time.Time }{
+ []time.Time{date, date},
+ },
+ wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n",
+ dateStr, dateStr),
+ },
+ "nested arrays and slices": {
+ input: struct {
+ SliceOfArrays [][2]int
+ ArrayOfSlices [2][]int
+ SliceOfArraysOfSlices [][2][]int
+ ArrayOfSlicesOfArrays [2][][2]int
+ SliceOfMixedArrays [][2]interface{}
+ ArrayOfMixedSlices [2][]interface{}
+ }{
+ [][2]int{{1, 2}, {3, 4}},
+ [2][]int{{1, 2}, {3, 4}},
+ [][2][]int{
+ {
+ {1, 2}, {3, 4},
+ },
+ {
+ {5, 6}, {7, 8},
+ },
+ },
+ [2][][2]int{
+ {
+ {1, 2}, {3, 4},
+ },
+ {
+ {5, 6}, {7, 8},
+ },
+ },
+ [][2]interface{}{
+ {1, 2}, {"a", "b"},
+ },
+ [2][]interface{}{
+ {1, 2}, {"a", "b"},
+ },
+ },
+ wantOutput: `SliceOfArrays = [[1, 2], [3, 4]]
+ArrayOfSlices = [[1, 2], [3, 4]]
+SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
+ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
+SliceOfMixedArrays = [[1, 2], ["a", "b"]]
+ArrayOfMixedSlices = [[1, 2], ["a", "b"]]
+`,
+ },
+ "empty slice": {
+ input: struct{ Empty []interface{} }{[]interface{}{}},
+ wantOutput: "Empty = []\n",
+ },
+ "(error) slice with element type mismatch (string and integer)": {
+ input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}},
+ wantError: errArrayMixedElementTypes,
+ },
+ "(error) slice with element type mismatch (integer and float)": {
+ input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}},
+ wantError: errArrayMixedElementTypes,
+ },
+ "slice with elems of differing Go types, same TOML types": {
+ input: struct {
+ MixedInts []interface{}
+ MixedFloats []interface{}
+ }{
+ []interface{}{
+ int(1), int8(2), int16(3), int32(4), int64(5),
+ uint(1), uint8(2), uint16(3), uint32(4), uint64(5),
+ },
+ []interface{}{float32(1.5), float64(2.5)},
+ },
+ wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" +
+ "MixedFloats = [1.5, 2.5]\n",
+ },
+ "(error) slice w/ element type mismatch (one is nested array)": {
+ input: struct{ Mixed []interface{} }{
+ []interface{}{1, []interface{}{2}},
+ },
+ wantError: errArrayMixedElementTypes,
+ },
+ "(error) slice with 1 nil element": {
+ input: struct{ NilElement1 []interface{} }{[]interface{}{nil}},
+ wantError: errArrayNilElement,
+ },
+ "(error) slice with 1 nil element (and other non-nil elements)": {
+ input: struct{ NilElement []interface{} }{
+ []interface{}{1, nil},
+ },
+ wantError: errArrayNilElement,
+ },
+ "simple map": {
+ input: map[string]int{"a": 1, "b": 2},
+ wantOutput: "a = 1\nb = 2\n",
+ },
+ "map with interface{} value type": {
+ input: map[string]interface{}{"a": 1, "b": "c"},
+ wantOutput: "a = 1\nb = \"c\"\n",
+ },
+ "map with interface{} value type, some of which are structs": {
+ input: map[string]interface{}{
+ "a": struct{ Int int }{2},
+ "b": 1,
+ },
+ wantOutput: "b = 1\n\n[a]\n Int = 2\n",
+ },
+ "nested map": {
+ input: map[string]map[string]int{
+ "a": {"b": 1},
+ "c": {"d": 2},
+ },
+ wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n",
+ },
+ "nested struct": {
+ input: struct{ Struct struct{ Int int } }{
+ struct{ Int int }{1},
+ },
+ wantOutput: "[Struct]\n Int = 1\n",
+ },
+ "nested struct and non-struct field": {
+ input: struct {
+ Struct struct{ Int int }
+ Bool bool
+ }{struct{ Int int }{1}, true},
+ wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n",
+ },
+ "2 nested structs": {
+ input: struct{ Struct1, Struct2 struct{ Int int } }{
+ struct{ Int int }{1}, struct{ Int int }{2},
+ },
+ wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n",
+ },
+ "deeply nested structs": {
+ input: struct {
+ Struct1, Struct2 struct{ Struct3 *struct{ Int int } }
+ }{
+ struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}},
+ struct{ Struct3 *struct{ Int int } }{nil},
+ },
+ wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" +
+ "\n\n[Struct2]\n",
+ },
+ "nested struct with nil struct elem": {
+ input: struct {
+ Struct struct{ Inner *struct{ Int int } }
+ }{
+ struct{ Inner *struct{ Int int } }{nil},
+ },
+ wantOutput: "[Struct]\n",
+ },
+ "nested struct with no fields": {
+ input: struct {
+ Struct struct{ Inner struct{} }
+ }{
+ struct{ Inner struct{} }{struct{}{}},
+ },
+ wantOutput: "[Struct]\n [Struct.Inner]\n",
+ },
+ "struct with tags": {
+ input: struct {
+ Struct struct {
+ Int int `toml:"_int"`
+ } `toml:"_struct"`
+ Bool bool `toml:"_bool"`
+ }{
+ struct {
+ Int int `toml:"_int"`
+ }{1}, true,
+ },
+ wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n",
+ },
+ "embedded struct": {
+ input: struct{ Embedded }{Embedded{1}},
+ wantOutput: "_int = 1\n",
+ },
+ "embedded *struct": {
+ input: struct{ *Embedded }{&Embedded{1}},
+ wantOutput: "_int = 1\n",
+ },
+ "nested embedded struct": {
+ input: struct {
+ Struct struct{ Embedded } `toml:"_struct"`
+ }{struct{ Embedded }{Embedded{1}}},
+ wantOutput: "[_struct]\n _int = 1\n",
+ },
+ "nested embedded *struct": {
+ input: struct {
+ Struct struct{ *Embedded } `toml:"_struct"`
+ }{struct{ *Embedded }{&Embedded{1}}},
+ wantOutput: "[_struct]\n _int = 1\n",
+ },
+ "embedded non-struct": {
+ input: struct{ NonStruct }{5},
+ wantOutput: "NonStruct = 5\n",
+ },
+ "array of tables": {
+ input: struct {
+ Structs []*struct{ Int int } `toml:"struct"`
+ }{
+ []*struct{ Int int }{{1}, {3}},
+ },
+ wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n",
+ },
+ "array of tables order": {
+ input: map[string]interface{}{
+ "map": map[string]interface{}{
+ "zero": 5,
+ "arr": []map[string]int{
+ {
+ "friend": 5,
+ },
+ },
+ },
+ },
+ wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n",
+ },
+ "(error) top-level slice": {
+ input: []struct{ Int int }{{1}, {2}, {3}},
+ wantError: errNoKey,
+ },
+ "(error) slice of slice": {
+ input: struct {
+ Slices [][]struct{ Int int }
+ }{
+ [][]struct{ Int int }{{{1}}, {{2}}, {{3}}},
+ },
+ wantError: errArrayNoTable,
+ },
+ "(error) map no string key": {
+ input: map[int]string{1: ""},
+ wantError: errNonString,
+ },
+ "(error) empty key name": {
+ input: map[string]int{"": 1},
+ wantError: errAnything,
+ },
+ "(error) empty map name": {
+ input: map[string]interface{}{
+ "": map[string]int{"v": 1},
+ },
+ wantError: errAnything,
+ },
+ }
+ for label, test := range tests {
+ encodeExpected(t, label, test.input, test.wantOutput, test.wantError)
+ }
+}
+
+func TestEncodeNestedTableArrays(t *testing.T) {
+ type song struct {
+ Name string `toml:"name"`
+ }
+ type album struct {
+ Name string `toml:"name"`
+ Songs []song `toml:"songs"`
+ }
+ type springsteen struct {
+ Albums []album `toml:"albums"`
+ }
+ value := springsteen{
+ []album{
+ {"Born to Run",
+ []song{{"Jungleland"}, {"Meeting Across the River"}}},
+ {"Born in the USA",
+ []song{{"Glory Days"}, {"Dancing in the Dark"}}},
+ },
+ }
+ expected := `[[albums]]
+ name = "Born to Run"
+
+ [[albums.songs]]
+ name = "Jungleland"
+
+ [[albums.songs]]
+ name = "Meeting Across the River"
+
+[[albums]]
+ name = "Born in the USA"
+
+ [[albums.songs]]
+ name = "Glory Days"
+
+ [[albums.songs]]
+ name = "Dancing in the Dark"
+`
+ encodeExpected(t, "nested table arrays", value, expected, nil)
+}
+
+func TestEncodeArrayHashWithNormalHashOrder(t *testing.T) {
+ type Alpha struct {
+ V int
+ }
+ type Beta struct {
+ V int
+ }
+ type Conf struct {
+ V int
+ A Alpha
+ B []Beta
+ }
+
+ val := Conf{
+ V: 1,
+ A: Alpha{2},
+ B: []Beta{{3}},
+ }
+ expected := "V = 1\n\n[A]\n V = 2\n\n[[B]]\n V = 3\n"
+ encodeExpected(t, "array hash with normal hash order", val, expected, nil)
+}
+
+func TestEncodeWithOmitEmpty(t *testing.T) {
+ type simple struct {
+ Bool bool `toml:"bool,omitempty"`
+ String string `toml:"string,omitempty"`
+ Array [0]byte `toml:"array,omitempty"`
+ Slice []int `toml:"slice,omitempty"`
+ Map map[string]string `toml:"map,omitempty"`
+ }
+
+ var v simple
+ encodeExpected(t, "fields with omitempty are omitted when empty", v, "", nil)
+ v = simple{
+ Bool: true,
+ String: " ",
+ Slice: []int{2, 3, 4},
+ Map: map[string]string{"foo": "bar"},
+ }
+ expected := `bool = true
+string = " "
+slice = [2, 3, 4]
+
+[map]
+ foo = "bar"
+`
+ encodeExpected(t, "fields with omitempty are not omitted when non-empty",
+ v, expected, nil)
+}
+
+func TestEncodeWithOmitZero(t *testing.T) {
+ type simple struct {
+ Number int `toml:"number,omitzero"`
+ Real float64 `toml:"real,omitzero"`
+ Unsigned uint `toml:"unsigned,omitzero"`
+ }
+
+ value := simple{0, 0.0, uint(0)}
+ expected := ""
+
+ encodeExpected(t, "simple with omitzero, all zero", value, expected, nil)
+
+ value.Number = 10
+ value.Real = 20
+ value.Unsigned = 5
+ expected = `number = 10
+real = 20.0
+unsigned = 5
+`
+ encodeExpected(t, "simple with omitzero, non-zero", value, expected, nil)
+}
+
+func TestEncodeOmitemptyWithEmptyName(t *testing.T) {
+ type simple struct {
+ S []int `toml:",omitempty"`
+ }
+ v := simple{[]int{1, 2, 3}}
+ expected := "S = [1, 2, 3]\n"
+ encodeExpected(t, "simple with omitempty, no name, non-empty field",
+ v, expected, nil)
+}
+
+func TestEncodeAnonymousStruct(t *testing.T) {
+ type Inner struct{ N int }
+ type Outer0 struct{ Inner }
+ type Outer1 struct {
+ Inner `toml:"inner"`
+ }
+
+ v0 := Outer0{Inner{3}}
+ expected := "N = 3\n"
+ encodeExpected(t, "embedded anonymous untagged struct", v0, expected, nil)
+
+ v1 := Outer1{Inner{3}}
+ expected = "[inner]\n N = 3\n"
+ encodeExpected(t, "embedded anonymous tagged struct", v1, expected, nil)
+}
+
+func TestEncodeAnonymousStructPointerField(t *testing.T) {
+ type Inner struct{ N int }
+ type Outer0 struct{ *Inner }
+ type Outer1 struct {
+ *Inner `toml:"inner"`
+ }
+
+ v0 := Outer0{}
+ expected := ""
+ encodeExpected(t, "nil anonymous untagged struct pointer field", v0, expected, nil)
+
+ v0 = Outer0{&Inner{3}}
+ expected = "N = 3\n"
+ encodeExpected(t, "non-nil anonymous untagged struct pointer field", v0, expected, nil)
+
+ v1 := Outer1{}
+ expected = ""
+ encodeExpected(t, "nil anonymous tagged struct pointer field", v1, expected, nil)
+
+ v1 = Outer1{&Inner{3}}
+ expected = "[inner]\n N = 3\n"
+ encodeExpected(t, "non-nil anonymous tagged struct pointer field", v1, expected, nil)
+}
+
+func TestEncodeIgnoredFields(t *testing.T) {
+ type simple struct {
+ Number int `toml:"-"`
+ }
+ value := simple{}
+ expected := ""
+ encodeExpected(t, "ignored field", value, expected, nil)
+}
+
+func encodeExpected(
+ t *testing.T, label string, val interface{}, wantStr string, wantErr error,
+) {
+ var buf bytes.Buffer
+ enc := NewEncoder(&buf)
+ err := enc.Encode(val)
+ if err != wantErr {
+ if wantErr != nil {
+ if wantErr == errAnything && err != nil {
+ return
+ }
+ t.Errorf("%s: want Encode error %v, got %v", label, wantErr, err)
+ } else {
+ t.Errorf("%s: Encode failed: %s", label, err)
+ }
+ }
+ if err != nil {
+ return
+ }
+ if got := buf.String(); wantStr != got {
+ t.Errorf("%s: want\n-----\n%q\n-----\nbut got\n-----\n%q\n-----\n",
+ label, wantStr, got)
+ }
+}
+
+func ExampleEncoder_Encode() {
+ date, _ := time.Parse(time.RFC822, "14 Mar 10 18:00 UTC")
+ var config = map[string]interface{}{
+ "date": date,
+ "counts": []int{1, 1, 2, 3, 5, 8},
+ "hash": map[string]string{
+ "key1": "val1",
+ "key2": "val2",
+ },
+ }
+ buf := new(bytes.Buffer)
+ if err := NewEncoder(buf).Encode(config); err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println(buf.String())
+
+ // Output:
+ // counts = [1, 1, 2, 3, 5, 8]
+ // date = 2010-03-14T18:00:00Z
+ //
+ // [hash]
+ // key1 = "val1"
+ // key2 = "val2"
+}
diff --git a/vendor/github.com/BurntSushi/toml/encoding_types.go b/vendor/github.com/BurntSushi/toml/encoding_types.go
new file mode 100644
index 0000000..d36e1dd
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/encoding_types.go
@@ -0,0 +1,19 @@
+// +build go1.2
+
+package toml
+
+// In order to support Go 1.1, we define our own TextMarshaler and
+// TextUnmarshaler types. For Go 1.2+, we just alias them with the
+// standard library interfaces.
+
+import (
+ "encoding"
+)
+
+// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
+// so that Go 1.1 can be supported.
+type TextMarshaler encoding.TextMarshaler
+
+// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
+// here so that Go 1.1 can be supported.
+type TextUnmarshaler encoding.TextUnmarshaler
diff --git a/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go b/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
new file mode 100644
index 0000000..e8d503d
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
@@ -0,0 +1,18 @@
+// +build !go1.2
+
+package toml
+
+// These interfaces were introduced in Go 1.2, so we add them manually when
+// compiling for Go 1.1.
+
+// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
+// so that Go 1.1 can be supported.
+type TextMarshaler interface {
+ MarshalText() (text []byte, err error)
+}
+
+// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
+// here so that Go 1.1 can be supported.
+type TextUnmarshaler interface {
+ UnmarshalText(text []byte) error
+}
diff --git a/vendor/github.com/BurntSushi/toml/lex.go b/vendor/github.com/BurntSushi/toml/lex.go
new file mode 100644
index 0000000..6dee7fc
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/lex.go
@@ -0,0 +1,953 @@
+package toml
+
+import (
+ "fmt"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+type itemType int
+
+const (
+ itemError itemType = iota
+ itemNIL // used in the parser to indicate no type
+ itemEOF
+ itemText
+ itemString
+ itemRawString
+ itemMultilineString
+ itemRawMultilineString
+ itemBool
+ itemInteger
+ itemFloat
+ itemDatetime
+ itemArray // the start of an array
+ itemArrayEnd
+ itemTableStart
+ itemTableEnd
+ itemArrayTableStart
+ itemArrayTableEnd
+ itemKeyStart
+ itemCommentStart
+ itemInlineTableStart
+ itemInlineTableEnd
+)
+
+const (
+ eof = 0
+ comma = ','
+ tableStart = '['
+ tableEnd = ']'
+ arrayTableStart = '['
+ arrayTableEnd = ']'
+ tableSep = '.'
+ keySep = '='
+ arrayStart = '['
+ arrayEnd = ']'
+ commentStart = '#'
+ stringStart = '"'
+ stringEnd = '"'
+ rawStringStart = '\''
+ rawStringEnd = '\''
+ inlineTableStart = '{'
+ inlineTableEnd = '}'
+)
+
+type stateFn func(lx *lexer) stateFn
+
+type lexer struct {
+ input string
+ start int
+ pos int
+ line int
+ state stateFn
+ items chan item
+
+ // Allow for backing up up to three runes.
+ // This is necessary because TOML contains 3-rune tokens (""" and ''').
+ prevWidths [3]int
+ nprev int // how many of prevWidths are in use
+ // If we emit an eof, we can still back up, but it is not OK to call
+ // next again.
+ atEOF bool
+
+ // A stack of state functions used to maintain context.
+ // The idea is to reuse parts of the state machine in various places.
+ // For example, values can appear at the top level or within arbitrarily
+ // nested arrays. The last state on the stack is used after a value has
+ // been lexed. Similarly for comments.
+ stack []stateFn
+}
+
+type item struct {
+ typ itemType
+ val string
+ line int
+}
+
+func (lx *lexer) nextItem() item {
+ for {
+ select {
+ case item := <-lx.items:
+ return item
+ default:
+ lx.state = lx.state(lx)
+ }
+ }
+}
+
+func lex(input string) *lexer {
+ lx := &lexer{
+ input: input,
+ state: lexTop,
+ line: 1,
+ items: make(chan item, 10),
+ stack: make([]stateFn, 0, 10),
+ }
+ return lx
+}
+
+func (lx *lexer) push(state stateFn) {
+ lx.stack = append(lx.stack, state)
+}
+
+func (lx *lexer) pop() stateFn {
+ if len(lx.stack) == 0 {
+ return lx.errorf("BUG in lexer: no states to pop")
+ }
+ last := lx.stack[len(lx.stack)-1]
+ lx.stack = lx.stack[0 : len(lx.stack)-1]
+ return last
+}
+
+func (lx *lexer) current() string {
+ return lx.input[lx.start:lx.pos]
+}
+
+func (lx *lexer) emit(typ itemType) {
+ lx.items <- item{typ, lx.current(), lx.line}
+ lx.start = lx.pos
+}
+
+func (lx *lexer) emitTrim(typ itemType) {
+ lx.items <- item{typ, strings.TrimSpace(lx.current()), lx.line}
+ lx.start = lx.pos
+}
+
+func (lx *lexer) next() (r rune) {
+ if lx.atEOF {
+ panic("next called after EOF")
+ }
+ if lx.pos >= len(lx.input) {
+ lx.atEOF = true
+ return eof
+ }
+
+ if lx.input[lx.pos] == '\n' {
+ lx.line++
+ }
+ lx.prevWidths[2] = lx.prevWidths[1]
+ lx.prevWidths[1] = lx.prevWidths[0]
+ if lx.nprev < 3 {
+ lx.nprev++
+ }
+ r, w := utf8.DecodeRuneInString(lx.input[lx.pos:])
+ lx.prevWidths[0] = w
+ lx.pos += w
+ return r
+}
+
+// ignore skips over the pending input before this point.
+func (lx *lexer) ignore() {
+ lx.start = lx.pos
+}
+
+// backup steps back one rune. Can be called only twice between calls to next.
+func (lx *lexer) backup() {
+ if lx.atEOF {
+ lx.atEOF = false
+ return
+ }
+ if lx.nprev < 1 {
+ panic("backed up too far")
+ }
+ w := lx.prevWidths[0]
+ lx.prevWidths[0] = lx.prevWidths[1]
+ lx.prevWidths[1] = lx.prevWidths[2]
+ lx.nprev--
+ lx.pos -= w
+ if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
+ lx.line--
+ }
+}
+
+// accept consumes the next rune if it's equal to `valid`.
+func (lx *lexer) accept(valid rune) bool {
+ if lx.next() == valid {
+ return true
+ }
+ lx.backup()
+ return false
+}
+
+// peek returns but does not consume the next rune in the input.
+func (lx *lexer) peek() rune {
+ r := lx.next()
+ lx.backup()
+ return r
+}
+
+// skip ignores all input that matches the given predicate.
+func (lx *lexer) skip(pred func(rune) bool) {
+ for {
+ r := lx.next()
+ if pred(r) {
+ continue
+ }
+ lx.backup()
+ lx.ignore()
+ return
+ }
+}
+
+// errorf stops all lexing by emitting an error and returning `nil`.
+// Note that any value that is a character is escaped if it's a special
+// character (newlines, tabs, etc.).
+func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
+ lx.items <- item{
+ itemError,
+ fmt.Sprintf(format, values...),
+ lx.line,
+ }
+ return nil
+}
+
+// lexTop consumes elements at the top level of TOML data.
+func lexTop(lx *lexer) stateFn {
+ r := lx.next()
+ if isWhitespace(r) || isNL(r) {
+ return lexSkip(lx, lexTop)
+ }
+ switch r {
+ case commentStart:
+ lx.push(lexTop)
+ return lexCommentStart
+ case tableStart:
+ return lexTableStart
+ case eof:
+ if lx.pos > lx.start {
+ return lx.errorf("unexpected EOF")
+ }
+ lx.emit(itemEOF)
+ return nil
+ }
+
+ // At this point, the only valid item can be a key, so we back up
+ // and let the key lexer do the rest.
+ lx.backup()
+ lx.push(lexTopEnd)
+ return lexKeyStart
+}
+
+// lexTopEnd is entered whenever a top-level item has been consumed. (A value
+// or a table.) It must see only whitespace, and will turn back to lexTop
+// upon a newline. If it sees EOF, it will quit the lexer successfully.
+func lexTopEnd(lx *lexer) stateFn {
+ r := lx.next()
+ switch {
+ case r == commentStart:
+ // a comment will read to a newline for us.
+ lx.push(lexTop)
+ return lexCommentStart
+ case isWhitespace(r):
+ return lexTopEnd
+ case isNL(r):
+ lx.ignore()
+ return lexTop
+ case r == eof:
+ lx.emit(itemEOF)
+ return nil
+ }
+ return lx.errorf("expected a top-level item to end with a newline, "+
+ "comment, or EOF, but got %q instead", r)
+}
+
+// lexTable lexes the beginning of a table. Namely, it makes sure that
+// it starts with a character other than '.' and ']'.
+// It assumes that '[' has already been consumed.
+// It also handles the case that this is an item in an array of tables.
+// e.g., '[[name]]'.
+func lexTableStart(lx *lexer) stateFn {
+ if lx.peek() == arrayTableStart {
+ lx.next()
+ lx.emit(itemArrayTableStart)
+ lx.push(lexArrayTableEnd)
+ } else {
+ lx.emit(itemTableStart)
+ lx.push(lexTableEnd)
+ }
+ return lexTableNameStart
+}
+
+func lexTableEnd(lx *lexer) stateFn {
+ lx.emit(itemTableEnd)
+ return lexTopEnd
+}
+
+func lexArrayTableEnd(lx *lexer) stateFn {
+ if r := lx.next(); r != arrayTableEnd {
+ return lx.errorf("expected end of table array name delimiter %q, "+
+ "but got %q instead", arrayTableEnd, r)
+ }
+ lx.emit(itemArrayTableEnd)
+ return lexTopEnd
+}
+
+func lexTableNameStart(lx *lexer) stateFn {
+ lx.skip(isWhitespace)
+ switch r := lx.peek(); {
+ case r == tableEnd || r == eof:
+ return lx.errorf("unexpected end of table name " +
+ "(table names cannot be empty)")
+ case r == tableSep:
+ return lx.errorf("unexpected table separator " +
+ "(table names cannot be empty)")
+ case r == stringStart || r == rawStringStart:
+ lx.ignore()
+ lx.push(lexTableNameEnd)
+ return lexValue // reuse string lexing
+ default:
+ return lexBareTableName
+ }
+}
+
+// lexBareTableName lexes the name of a table. It assumes that at least one
+// valid character for the table has already been read.
+func lexBareTableName(lx *lexer) stateFn {
+ r := lx.next()
+ if isBareKeyChar(r) {
+ return lexBareTableName
+ }
+ lx.backup()
+ lx.emit(itemText)
+ return lexTableNameEnd
+}
+
+// lexTableNameEnd reads the end of a piece of a table name, optionally
+// consuming whitespace.
+func lexTableNameEnd(lx *lexer) stateFn {
+ lx.skip(isWhitespace)
+ switch r := lx.next(); {
+ case isWhitespace(r):
+ return lexTableNameEnd
+ case r == tableSep:
+ lx.ignore()
+ return lexTableNameStart
+ case r == tableEnd:
+ return lx.pop()
+ default:
+ return lx.errorf("expected '.' or ']' to end table name, "+
+ "but got %q instead", r)
+ }
+}
+
+// lexKeyStart consumes a key name up until the first non-whitespace character.
+// lexKeyStart will ignore whitespace.
+func lexKeyStart(lx *lexer) stateFn {
+ r := lx.peek()
+ switch {
+ case r == keySep:
+ return lx.errorf("unexpected key separator %q", keySep)
+ case isWhitespace(r) || isNL(r):
+ lx.next()
+ return lexSkip(lx, lexKeyStart)
+ case r == stringStart || r == rawStringStart:
+ lx.ignore()
+ lx.emit(itemKeyStart)
+ lx.push(lexKeyEnd)
+ return lexValue // reuse string lexing
+ default:
+ lx.ignore()
+ lx.emit(itemKeyStart)
+ return lexBareKey
+ }
+}
+
+// lexBareKey consumes the text of a bare key. Assumes that the first character
+// (which is not whitespace) has not yet been consumed.
+func lexBareKey(lx *lexer) stateFn {
+ switch r := lx.next(); {
+ case isBareKeyChar(r):
+ return lexBareKey
+ case isWhitespace(r):
+ lx.backup()
+ lx.emit(itemText)
+ return lexKeyEnd
+ case r == keySep:
+ lx.backup()
+ lx.emit(itemText)
+ return lexKeyEnd
+ default:
+ return lx.errorf("bare keys cannot contain %q", r)
+ }
+}
+
+// lexKeyEnd consumes the end of a key and trims whitespace (up to the key
+// separator).
+func lexKeyEnd(lx *lexer) stateFn {
+ switch r := lx.next(); {
+ case r == keySep:
+ return lexSkip(lx, lexValue)
+ case isWhitespace(r):
+ return lexSkip(lx, lexKeyEnd)
+ default:
+ return lx.errorf("expected key separator %q, but got %q instead",
+ keySep, r)
+ }
+}
+
+// lexValue starts the consumption of a value anywhere a value is expected.
+// lexValue will ignore whitespace.
+// After a value is lexed, the last state on the next is popped and returned.
+func lexValue(lx *lexer) stateFn {
+ // We allow whitespace to precede a value, but NOT newlines.
+ // In array syntax, the array states are responsible for ignoring newlines.
+ r := lx.next()
+ switch {
+ case isWhitespace(r):
+ return lexSkip(lx, lexValue)
+ case isDigit(r):
+ lx.backup() // avoid an extra state and use the same as above
+ return lexNumberOrDateStart
+ }
+ switch r {
+ case arrayStart:
+ lx.ignore()
+ lx.emit(itemArray)
+ return lexArrayValue
+ case inlineTableStart:
+ lx.ignore()
+ lx.emit(itemInlineTableStart)
+ return lexInlineTableValue
+ case stringStart:
+ if lx.accept(stringStart) {
+ if lx.accept(stringStart) {
+ lx.ignore() // Ignore """
+ return lexMultilineString
+ }
+ lx.backup()
+ }
+ lx.ignore() // ignore the '"'
+ return lexString
+ case rawStringStart:
+ if lx.accept(rawStringStart) {
+ if lx.accept(rawStringStart) {
+ lx.ignore() // Ignore """
+ return lexMultilineRawString
+ }
+ lx.backup()
+ }
+ lx.ignore() // ignore the "'"
+ return lexRawString
+ case '+', '-':
+ return lexNumberStart
+ case '.': // special error case, be kind to users
+ return lx.errorf("floats must start with a digit, not '.'")
+ }
+ if unicode.IsLetter(r) {
+ // Be permissive here; lexBool will give a nice error if the
+ // user wrote something like
+ // x = foo
+ // (i.e. not 'true' or 'false' but is something else word-like.)
+ lx.backup()
+ return lexBool
+ }
+ return lx.errorf("expected value but found %q instead", r)
+}
+
+// lexArrayValue consumes one value in an array. It assumes that '[' or ','
+// have already been consumed. All whitespace and newlines are ignored.
+func lexArrayValue(lx *lexer) stateFn {
+ r := lx.next()
+ switch {
+ case isWhitespace(r) || isNL(r):
+ return lexSkip(lx, lexArrayValue)
+ case r == commentStart:
+ lx.push(lexArrayValue)
+ return lexCommentStart
+ case r == comma:
+ return lx.errorf("unexpected comma")
+ case r == arrayEnd:
+ // NOTE(caleb): The spec isn't clear about whether you can have
+ // a trailing comma or not, so we'll allow it.
+ return lexArrayEnd
+ }
+
+ lx.backup()
+ lx.push(lexArrayValueEnd)
+ return lexValue
+}
+
+// lexArrayValueEnd consumes everything between the end of an array value and
+// the next value (or the end of the array): it ignores whitespace and newlines
+// and expects either a ',' or a ']'.
+func lexArrayValueEnd(lx *lexer) stateFn {
+ r := lx.next()
+ switch {
+ case isWhitespace(r) || isNL(r):
+ return lexSkip(lx, lexArrayValueEnd)
+ case r == commentStart:
+ lx.push(lexArrayValueEnd)
+ return lexCommentStart
+ case r == comma:
+ lx.ignore()
+ return lexArrayValue // move on to the next value
+ case r == arrayEnd:
+ return lexArrayEnd
+ }
+ return lx.errorf(
+ "expected a comma or array terminator %q, but got %q instead",
+ arrayEnd, r,
+ )
+}
+
+// lexArrayEnd finishes the lexing of an array.
+// It assumes that a ']' has just been consumed.
+func lexArrayEnd(lx *lexer) stateFn {
+ lx.ignore()
+ lx.emit(itemArrayEnd)
+ return lx.pop()
+}
+
+// lexInlineTableValue consumes one key/value pair in an inline table.
+// It assumes that '{' or ',' have already been consumed. Whitespace is ignored.
+func lexInlineTableValue(lx *lexer) stateFn {
+ r := lx.next()
+ switch {
+ case isWhitespace(r):
+ return lexSkip(lx, lexInlineTableValue)
+ case isNL(r):
+ return lx.errorf("newlines not allowed within inline tables")
+ case r == commentStart:
+ lx.push(lexInlineTableValue)
+ return lexCommentStart
+ case r == comma:
+ return lx.errorf("unexpected comma")
+ case r == inlineTableEnd:
+ return lexInlineTableEnd
+ }
+ lx.backup()
+ lx.push(lexInlineTableValueEnd)
+ return lexKeyStart
+}
+
+// lexInlineTableValueEnd consumes everything between the end of an inline table
+// key/value pair and the next pair (or the end of the table):
+// it ignores whitespace and expects either a ',' or a '}'.
+func lexInlineTableValueEnd(lx *lexer) stateFn {
+ r := lx.next()
+ switch {
+ case isWhitespace(r):
+ return lexSkip(lx, lexInlineTableValueEnd)
+ case isNL(r):
+ return lx.errorf("newlines not allowed within inline tables")
+ case r == commentStart:
+ lx.push(lexInlineTableValueEnd)
+ return lexCommentStart
+ case r == comma:
+ lx.ignore()
+ return lexInlineTableValue
+ case r == inlineTableEnd:
+ return lexInlineTableEnd
+ }
+ return lx.errorf("expected a comma or an inline table terminator %q, "+
+ "but got %q instead", inlineTableEnd, r)
+}
+
+// lexInlineTableEnd finishes the lexing of an inline table.
+// It assumes that a '}' has just been consumed.
+func lexInlineTableEnd(lx *lexer) stateFn {
+ lx.ignore()
+ lx.emit(itemInlineTableEnd)
+ return lx.pop()
+}
+
+// lexString consumes the inner contents of a string. It assumes that the
+// beginning '"' has already been consumed and ignored.
+func lexString(lx *lexer) stateFn {
+ r := lx.next()
+ switch {
+ case r == eof:
+ return lx.errorf("unexpected EOF")
+ case isNL(r):
+ return lx.errorf("strings cannot contain newlines")
+ case r == '\\':
+ lx.push(lexString)
+ return lexStringEscape
+ case r == stringEnd:
+ lx.backup()
+ lx.emit(itemString)
+ lx.next()
+ lx.ignore()
+ return lx.pop()
+ }
+ return lexString
+}
+
+// lexMultilineString consumes the inner contents of a string. It assumes that
+// the beginning '"""' has already been consumed and ignored.
+func lexMultilineString(lx *lexer) stateFn {
+ switch lx.next() {
+ case eof:
+ return lx.errorf("unexpected EOF")
+ case '\\':
+ return lexMultilineStringEscape
+ case stringEnd:
+ if lx.accept(stringEnd) {
+ if lx.accept(stringEnd) {
+ lx.backup()
+ lx.backup()
+ lx.backup()
+ lx.emit(itemMultilineString)
+ lx.next()
+ lx.next()
+ lx.next()
+ lx.ignore()
+ return lx.pop()
+ }
+ lx.backup()
+ }
+ }
+ return lexMultilineString
+}
+
+// lexRawString consumes a raw string. Nothing can be escaped in such a string.
+// It assumes that the beginning "'" has already been consumed and ignored.
+func lexRawString(lx *lexer) stateFn {
+ r := lx.next()
+ switch {
+ case r == eof:
+ return lx.errorf("unexpected EOF")
+ case isNL(r):
+ return lx.errorf("strings cannot contain newlines")
+ case r == rawStringEnd:
+ lx.backup()
+ lx.emit(itemRawString)
+ lx.next()
+ lx.ignore()
+ return lx.pop()
+ }
+ return lexRawString
+}
+
+// lexMultilineRawString consumes a raw string. Nothing can be escaped in such
+// a string. It assumes that the beginning "'''" has already been consumed and
+// ignored.
+func lexMultilineRawString(lx *lexer) stateFn {
+ switch lx.next() {
+ case eof:
+ return lx.errorf("unexpected EOF")
+ case rawStringEnd:
+ if lx.accept(rawStringEnd) {
+ if lx.accept(rawStringEnd) {
+ lx.backup()
+ lx.backup()
+ lx.backup()
+ lx.emit(itemRawMultilineString)
+ lx.next()
+ lx.next()
+ lx.next()
+ lx.ignore()
+ return lx.pop()
+ }
+ lx.backup()
+ }
+ }
+ return lexMultilineRawString
+}
+
+// lexMultilineStringEscape consumes an escaped character. It assumes that the
+// preceding '\\' has already been consumed.
+func lexMultilineStringEscape(lx *lexer) stateFn {
+ // Handle the special case first:
+ if isNL(lx.next()) {
+ return lexMultilineString
+ }
+ lx.backup()
+ lx.push(lexMultilineString)
+ return lexStringEscape(lx)
+}
+
+func lexStringEscape(lx *lexer) stateFn {
+ r := lx.next()
+ switch r {
+ case 'b':
+ fallthrough
+ case 't':
+ fallthrough
+ case 'n':
+ fallthrough
+ case 'f':
+ fallthrough
+ case 'r':
+ fallthrough
+ case '"':
+ fallthrough
+ case '\\':
+ return lx.pop()
+ case 'u':
+ return lexShortUnicodeEscape
+ case 'U':
+ return lexLongUnicodeEscape
+ }
+ return lx.errorf("invalid escape character %q; only the following "+
+ "escape characters are allowed: "+
+ `\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r)
+}
+
+func lexShortUnicodeEscape(lx *lexer) stateFn {
+ var r rune
+ for i := 0; i < 4; i++ {
+ r = lx.next()
+ if !isHexadecimal(r) {
+ return lx.errorf(`expected four hexadecimal digits after '\u', `+
+ "but got %q instead", lx.current())
+ }
+ }
+ return lx.pop()
+}
+
+func lexLongUnicodeEscape(lx *lexer) stateFn {
+ var r rune
+ for i := 0; i < 8; i++ {
+ r = lx.next()
+ if !isHexadecimal(r) {
+ return lx.errorf(`expected eight hexadecimal digits after '\U', `+
+ "but got %q instead", lx.current())
+ }
+ }
+ return lx.pop()
+}
+
+// lexNumberOrDateStart consumes either an integer, a float, or datetime.
+func lexNumberOrDateStart(lx *lexer) stateFn {
+ r := lx.next()
+ if isDigit(r) {
+ return lexNumberOrDate
+ }
+ switch r {
+ case '_':
+ return lexNumber
+ case 'e', 'E':
+ return lexFloat
+ case '.':
+ return lx.errorf("floats must start with a digit, not '.'")
+ }
+ return lx.errorf("expected a digit but got %q", r)
+}
+
+// lexNumberOrDate consumes either an integer, float or datetime.
+func lexNumberOrDate(lx *lexer) stateFn {
+ r := lx.next()
+ if isDigit(r) {
+ return lexNumberOrDate
+ }
+ switch r {
+ case '-':
+ return lexDatetime
+ case '_':
+ return lexNumber
+ case '.', 'e', 'E':
+ return lexFloat
+ }
+
+ lx.backup()
+ lx.emit(itemInteger)
+ return lx.pop()
+}
+
+// lexDatetime consumes a Datetime, to a first approximation.
+// The parser validates that it matches one of the accepted formats.
+func lexDatetime(lx *lexer) stateFn {
+ r := lx.next()
+ if isDigit(r) {
+ return lexDatetime
+ }
+ switch r {
+ case '-', 'T', ':', '.', 'Z':
+ return lexDatetime
+ }
+
+ lx.backup()
+ lx.emit(itemDatetime)
+ return lx.pop()
+}
+
+// lexNumberStart consumes either an integer or a float. It assumes that a sign
+// has already been read, but that *no* digits have been consumed.
+// lexNumberStart will move to the appropriate integer or float states.
+func lexNumberStart(lx *lexer) stateFn {
+ // We MUST see a digit. Even floats have to start with a digit.
+ r := lx.next()
+ if !isDigit(r) {
+ if r == '.' {
+ return lx.errorf("floats must start with a digit, not '.'")
+ }
+ return lx.errorf("expected a digit but got %q", r)
+ }
+ return lexNumber
+}
+
+// lexNumber consumes an integer or a float after seeing the first digit.
+func lexNumber(lx *lexer) stateFn {
+ r := lx.next()
+ if isDigit(r) {
+ return lexNumber
+ }
+ switch r {
+ case '_':
+ return lexNumber
+ case '.', 'e', 'E':
+ return lexFloat
+ }
+
+ lx.backup()
+ lx.emit(itemInteger)
+ return lx.pop()
+}
+
+// lexFloat consumes the elements of a float. It allows any sequence of
+// float-like characters, so floats emitted by the lexer are only a first
+// approximation and must be validated by the parser.
+func lexFloat(lx *lexer) stateFn {
+ r := lx.next()
+ if isDigit(r) {
+ return lexFloat
+ }
+ switch r {
+ case '_', '.', '-', '+', 'e', 'E':
+ return lexFloat
+ }
+
+ lx.backup()
+ lx.emit(itemFloat)
+ return lx.pop()
+}
+
+// lexBool consumes a bool string: 'true' or 'false.
+func lexBool(lx *lexer) stateFn {
+ var rs []rune
+ for {
+ r := lx.next()
+ if !unicode.IsLetter(r) {
+ lx.backup()
+ break
+ }
+ rs = append(rs, r)
+ }
+ s := string(rs)
+ switch s {
+ case "true", "false":
+ lx.emit(itemBool)
+ return lx.pop()
+ }
+ return lx.errorf("expected value but found %q instead", s)
+}
+
+// lexCommentStart begins the lexing of a comment. It will emit
+// itemCommentStart and consume no characters, passing control to lexComment.
+func lexCommentStart(lx *lexer) stateFn {
+ lx.ignore()
+ lx.emit(itemCommentStart)
+ return lexComment
+}
+
+// lexComment lexes an entire comment. It assumes that '#' has been consumed.
+// It will consume *up to* the first newline character, and pass control
+// back to the last state on the stack.
+func lexComment(lx *lexer) stateFn {
+ r := lx.peek()
+ if isNL(r) || r == eof {
+ lx.emit(itemText)
+ return lx.pop()
+ }
+ lx.next()
+ return lexComment
+}
+
+// lexSkip ignores all slurped input and moves on to the next state.
+func lexSkip(lx *lexer, nextState stateFn) stateFn {
+ return func(lx *lexer) stateFn {
+ lx.ignore()
+ return nextState
+ }
+}
+
+// isWhitespace returns true if `r` is a whitespace character according
+// to the spec.
+func isWhitespace(r rune) bool {
+ return r == '\t' || r == ' '
+}
+
+func isNL(r rune) bool {
+ return r == '\n' || r == '\r'
+}
+
+func isDigit(r rune) bool {
+ return r >= '0' && r <= '9'
+}
+
+func isHexadecimal(r rune) bool {
+ return (r >= '0' && r <= '9') ||
+ (r >= 'a' && r <= 'f') ||
+ (r >= 'A' && r <= 'F')
+}
+
+func isBareKeyChar(r rune) bool {
+ return (r >= 'A' && r <= 'Z') ||
+ (r >= 'a' && r <= 'z') ||
+ (r >= '0' && r <= '9') ||
+ r == '_' ||
+ r == '-'
+}
+
+func (itype itemType) String() string {
+ switch itype {
+ case itemError:
+ return "Error"
+ case itemNIL:
+ return "NIL"
+ case itemEOF:
+ return "EOF"
+ case itemText:
+ return "Text"
+ case itemString, itemRawString, itemMultilineString, itemRawMultilineString:
+ return "String"
+ case itemBool:
+ return "Bool"
+ case itemInteger:
+ return "Integer"
+ case itemFloat:
+ return "Float"
+ case itemDatetime:
+ return "DateTime"
+ case itemTableStart:
+ return "TableStart"
+ case itemTableEnd:
+ return "TableEnd"
+ case itemKeyStart:
+ return "KeyStart"
+ case itemArray:
+ return "Array"
+ case itemArrayEnd:
+ return "ArrayEnd"
+ case itemCommentStart:
+ return "CommentStart"
+ }
+ panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype)))
+}
+
+func (item item) String() string {
+ return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val)
+}
diff --git a/vendor/github.com/BurntSushi/toml/parse.go b/vendor/github.com/BurntSushi/toml/parse.go
new file mode 100644
index 0000000..50869ef
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/parse.go
@@ -0,0 +1,592 @@
+package toml
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+ "unicode"
+ "unicode/utf8"
+)
+
+type parser struct {
+ mapping map[string]interface{}
+ types map[string]tomlType
+ lx *lexer
+
+ // A list of keys in the order that they appear in the TOML data.
+ ordered []Key
+
+ // the full key for the current hash in scope
+ context Key
+
+ // the base key name for everything except hashes
+ currentKey string
+
+ // rough approximation of line number
+ approxLine int
+
+ // A map of 'key.group.names' to whether they were created implicitly.
+ implicits map[string]bool
+}
+
+type parseError string
+
+func (pe parseError) Error() string {
+ return string(pe)
+}
+
+func parse(data string) (p *parser, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ var ok bool
+ if err, ok = r.(parseError); ok {
+ return
+ }
+ panic(r)
+ }
+ }()
+
+ p = &parser{
+ mapping: make(map[string]interface{}),
+ types: make(map[string]tomlType),
+ lx: lex(data),
+ ordered: make([]Key, 0),
+ implicits: make(map[string]bool),
+ }
+ for {
+ item := p.next()
+ if item.typ == itemEOF {
+ break
+ }
+ p.topLevel(item)
+ }
+
+ return p, nil
+}
+
+func (p *parser) panicf(format string, v ...interface{}) {
+ msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s",
+ p.approxLine, p.current(), fmt.Sprintf(format, v...))
+ panic(parseError(msg))
+}
+
+func (p *parser) next() item {
+ it := p.lx.nextItem()
+ if it.typ == itemError {
+ p.panicf("%s", it.val)
+ }
+ return it
+}
+
+func (p *parser) bug(format string, v ...interface{}) {
+ panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
+}
+
+func (p *parser) expect(typ itemType) item {
+ it := p.next()
+ p.assertEqual(typ, it.typ)
+ return it
+}
+
+func (p *parser) assertEqual(expected, got itemType) {
+ if expected != got {
+ p.bug("Expected '%s' but got '%s'.", expected, got)
+ }
+}
+
+func (p *parser) topLevel(item item) {
+ switch item.typ {
+ case itemCommentStart:
+ p.approxLine = item.line
+ p.expect(itemText)
+ case itemTableStart:
+ kg := p.next()
+ p.approxLine = kg.line
+
+ var key Key
+ for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() {
+ key = append(key, p.keyString(kg))
+ }
+ p.assertEqual(itemTableEnd, kg.typ)
+
+ p.establishContext(key, false)
+ p.setType("", tomlHash)
+ p.ordered = append(p.ordered, key)
+ case itemArrayTableStart:
+ kg := p.next()
+ p.approxLine = kg.line
+
+ var key Key
+ for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() {
+ key = append(key, p.keyString(kg))
+ }
+ p.assertEqual(itemArrayTableEnd, kg.typ)
+
+ p.establishContext(key, true)
+ p.setType("", tomlArrayHash)
+ p.ordered = append(p.ordered, key)
+ case itemKeyStart:
+ kname := p.next()
+ p.approxLine = kname.line
+ p.currentKey = p.keyString(kname)
+
+ val, typ := p.value(p.next())
+ p.setValue(p.currentKey, val)
+ p.setType(p.currentKey, typ)
+ p.ordered = append(p.ordered, p.context.add(p.currentKey))
+ p.currentKey = ""
+ default:
+ p.bug("Unexpected type at top level: %s", item.typ)
+ }
+}
+
+// Gets a string for a key (or part of a key in a table name).
+func (p *parser) keyString(it item) string {
+ switch it.typ {
+ case itemText:
+ return it.val
+ case itemString, itemMultilineString,
+ itemRawString, itemRawMultilineString:
+ s, _ := p.value(it)
+ return s.(string)
+ default:
+ p.bug("Unexpected key type: %s", it.typ)
+ panic("unreachable")
+ }
+}
+
+// value translates an expected value from the lexer into a Go value wrapped
+// as an empty interface.
+func (p *parser) value(it item) (interface{}, tomlType) {
+ switch it.typ {
+ case itemString:
+ return p.replaceEscapes(it.val), p.typeOfPrimitive(it)
+ case itemMultilineString:
+ trimmed := stripFirstNewline(stripEscapedWhitespace(it.val))
+ return p.replaceEscapes(trimmed), p.typeOfPrimitive(it)
+ case itemRawString:
+ return it.val, p.typeOfPrimitive(it)
+ case itemRawMultilineString:
+ return stripFirstNewline(it.val), p.typeOfPrimitive(it)
+ case itemBool:
+ switch it.val {
+ case "true":
+ return true, p.typeOfPrimitive(it)
+ case "false":
+ return false, p.typeOfPrimitive(it)
+ }
+ p.bug("Expected boolean value, but got '%s'.", it.val)
+ case itemInteger:
+ if !numUnderscoresOK(it.val) {
+ p.panicf("Invalid integer %q: underscores must be surrounded by digits",
+ it.val)
+ }
+ val := strings.Replace(it.val, "_", "", -1)
+ num, err := strconv.ParseInt(val, 10, 64)
+ if err != nil {
+ // Distinguish integer values. Normally, it'd be a bug if the lexer
+ // provides an invalid integer, but it's possible that the number is
+ // out of range of valid values (which the lexer cannot determine).
+ // So mark the former as a bug but the latter as a legitimate user
+ // error.
+ if e, ok := err.(*strconv.NumError); ok &&
+ e.Err == strconv.ErrRange {
+
+ p.panicf("Integer '%s' is out of the range of 64-bit "+
+ "signed integers.", it.val)
+ } else {
+ p.bug("Expected integer value, but got '%s'.", it.val)
+ }
+ }
+ return num, p.typeOfPrimitive(it)
+ case itemFloat:
+ parts := strings.FieldsFunc(it.val, func(r rune) bool {
+ switch r {
+ case '.', 'e', 'E':
+ return true
+ }
+ return false
+ })
+ for _, part := range parts {
+ if !numUnderscoresOK(part) {
+ p.panicf("Invalid float %q: underscores must be "+
+ "surrounded by digits", it.val)
+ }
+ }
+ if !numPeriodsOK(it.val) {
+ // As a special case, numbers like '123.' or '1.e2',
+ // which are valid as far as Go/strconv are concerned,
+ // must be rejected because TOML says that a fractional
+ // part consists of '.' followed by 1+ digits.
+ p.panicf("Invalid float %q: '.' must be followed "+
+ "by one or more digits", it.val)
+ }
+ val := strings.Replace(it.val, "_", "", -1)
+ num, err := strconv.ParseFloat(val, 64)
+ if err != nil {
+ if e, ok := err.(*strconv.NumError); ok &&
+ e.Err == strconv.ErrRange {
+
+ p.panicf("Float '%s' is out of the range of 64-bit "+
+ "IEEE-754 floating-point numbers.", it.val)
+ } else {
+ p.panicf("Invalid float value: %q", it.val)
+ }
+ }
+ return num, p.typeOfPrimitive(it)
+ case itemDatetime:
+ var t time.Time
+ var ok bool
+ var err error
+ for _, format := range []string{
+ "2006-01-02T15:04:05Z07:00",
+ "2006-01-02T15:04:05",
+ "2006-01-02",
+ } {
+ t, err = time.ParseInLocation(format, it.val, time.Local)
+ if err == nil {
+ ok = true
+ break
+ }
+ }
+ if !ok {
+ p.panicf("Invalid TOML Datetime: %q.", it.val)
+ }
+ return t, p.typeOfPrimitive(it)
+ case itemArray:
+ array := make([]interface{}, 0)
+ types := make([]tomlType, 0)
+
+ for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
+ if it.typ == itemCommentStart {
+ p.expect(itemText)
+ continue
+ }
+
+ val, typ := p.value(it)
+ array = append(array, val)
+ types = append(types, typ)
+ }
+ return array, p.typeOfArray(types)
+ case itemInlineTableStart:
+ var (
+ hash = make(map[string]interface{})
+ outerContext = p.context
+ outerKey = p.currentKey
+ )
+
+ p.context = append(p.context, p.currentKey)
+ p.currentKey = ""
+ for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
+ if it.typ != itemKeyStart {
+ p.bug("Expected key start but instead found %q, around line %d",
+ it.val, p.approxLine)
+ }
+ if it.typ == itemCommentStart {
+ p.expect(itemText)
+ continue
+ }
+
+ // retrieve key
+ k := p.next()
+ p.approxLine = k.line
+ kname := p.keyString(k)
+
+ // retrieve value
+ p.currentKey = kname
+ val, typ := p.value(p.next())
+ // make sure we keep metadata up to date
+ p.setType(kname, typ)
+ p.ordered = append(p.ordered, p.context.add(p.currentKey))
+ hash[kname] = val
+ }
+ p.context = outerContext
+ p.currentKey = outerKey
+ return hash, tomlHash
+ }
+ p.bug("Unexpected value type: %s", it.typ)
+ panic("unreachable")
+}
+
+// numUnderscoresOK checks whether each underscore in s is surrounded by
+// characters that are not underscores.
+func numUnderscoresOK(s string) bool {
+ accept := false
+ for _, r := range s {
+ if r == '_' {
+ if !accept {
+ return false
+ }
+ accept = false
+ continue
+ }
+ accept = true
+ }
+ return accept
+}
+
+// numPeriodsOK checks whether every period in s is followed by a digit.
+func numPeriodsOK(s string) bool {
+ period := false
+ for _, r := range s {
+ if period && !isDigit(r) {
+ return false
+ }
+ period = r == '.'
+ }
+ return !period
+}
+
+// establishContext sets the current context of the parser,
+// where the context is either a hash or an array of hashes. Which one is
+// set depends on the value of the `array` parameter.
+//
+// Establishing the context also makes sure that the key isn't a duplicate, and
+// will create implicit hashes automatically.
+func (p *parser) establishContext(key Key, array bool) {
+ var ok bool
+
+ // Always start at the top level and drill down for our context.
+ hashContext := p.mapping
+ keyContext := make(Key, 0)
+
+ // We only need implicit hashes for key[0:-1]
+ for _, k := range key[0 : len(key)-1] {
+ _, ok = hashContext[k]
+ keyContext = append(keyContext, k)
+
+ // No key? Make an implicit hash and move on.
+ if !ok {
+ p.addImplicit(keyContext)
+ hashContext[k] = make(map[string]interface{})
+ }
+
+ // If the hash context is actually an array of tables, then set
+ // the hash context to the last element in that array.
+ //
+ // Otherwise, it better be a table, since this MUST be a key group (by
+ // virtue of it not being the last element in a key).
+ switch t := hashContext[k].(type) {
+ case []map[string]interface{}:
+ hashContext = t[len(t)-1]
+ case map[string]interface{}:
+ hashContext = t
+ default:
+ p.panicf("Key '%s' was already created as a hash.", keyContext)
+ }
+ }
+
+ p.context = keyContext
+ if array {
+ // If this is the first element for this array, then allocate a new
+ // list of tables for it.
+ k := key[len(key)-1]
+ if _, ok := hashContext[k]; !ok {
+ hashContext[k] = make([]map[string]interface{}, 0, 5)
+ }
+
+ // Add a new table. But make sure the key hasn't already been used
+ // for something else.
+ if hash, ok := hashContext[k].([]map[string]interface{}); ok {
+ hashContext[k] = append(hash, make(map[string]interface{}))
+ } else {
+ p.panicf("Key '%s' was already created and cannot be used as "+
+ "an array.", keyContext)
+ }
+ } else {
+ p.setValue(key[len(key)-1], make(map[string]interface{}))
+ }
+ p.context = append(p.context, key[len(key)-1])
+}
+
+// setValue sets the given key to the given value in the current context.
+// It will make sure that the key hasn't already been defined, account for
+// implicit key groups.
+func (p *parser) setValue(key string, value interface{}) {
+ var tmpHash interface{}
+ var ok bool
+
+ hash := p.mapping
+ keyContext := make(Key, 0)
+ for _, k := range p.context {
+ keyContext = append(keyContext, k)
+ if tmpHash, ok = hash[k]; !ok {
+ p.bug("Context for key '%s' has not been established.", keyContext)
+ }
+ switch t := tmpHash.(type) {
+ case []map[string]interface{}:
+ // The context is a table of hashes. Pick the most recent table
+ // defined as the current hash.
+ hash = t[len(t)-1]
+ case map[string]interface{}:
+ hash = t
+ default:
+ p.bug("Expected hash to have type 'map[string]interface{}', but "+
+ "it has '%T' instead.", tmpHash)
+ }
+ }
+ keyContext = append(keyContext, key)
+
+ if _, ok := hash[key]; ok {
+ // Typically, if the given key has already been set, then we have
+ // to raise an error since duplicate keys are disallowed. However,
+ // it's possible that a key was previously defined implicitly. In this
+ // case, it is allowed to be redefined concretely. (See the
+ // `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.)
+ //
+ // But we have to make sure to stop marking it as an implicit. (So that
+ // another redefinition provokes an error.)
+ //
+ // Note that since it has already been defined (as a hash), we don't
+ // want to overwrite it. So our business is done.
+ if p.isImplicit(keyContext) {
+ p.removeImplicit(keyContext)
+ return
+ }
+
+ // Otherwise, we have a concrete key trying to override a previous
+ // key, which is *always* wrong.
+ p.panicf("Key '%s' has already been defined.", keyContext)
+ }
+ hash[key] = value
+}
+
+// setType sets the type of a particular value at a given key.
+// It should be called immediately AFTER setValue.
+//
+// Note that if `key` is empty, then the type given will be applied to the
+// current context (which is either a table or an array of tables).
+func (p *parser) setType(key string, typ tomlType) {
+ keyContext := make(Key, 0, len(p.context)+1)
+ for _, k := range p.context {
+ keyContext = append(keyContext, k)
+ }
+ if len(key) > 0 { // allow type setting for hashes
+ keyContext = append(keyContext, key)
+ }
+ p.types[keyContext.String()] = typ
+}
+
+// addImplicit sets the given Key as having been created implicitly.
+func (p *parser) addImplicit(key Key) {
+ p.implicits[key.String()] = true
+}
+
+// removeImplicit stops tagging the given key as having been implicitly
+// created.
+func (p *parser) removeImplicit(key Key) {
+ p.implicits[key.String()] = false
+}
+
+// isImplicit returns true if the key group pointed to by the key was created
+// implicitly.
+func (p *parser) isImplicit(key Key) bool {
+ return p.implicits[key.String()]
+}
+
+// current returns the full key name of the current context.
+func (p *parser) current() string {
+ if len(p.currentKey) == 0 {
+ return p.context.String()
+ }
+ if len(p.context) == 0 {
+ return p.currentKey
+ }
+ return fmt.Sprintf("%s.%s", p.context, p.currentKey)
+}
+
+func stripFirstNewline(s string) string {
+ if len(s) == 0 || s[0] != '\n' {
+ return s
+ }
+ return s[1:]
+}
+
+func stripEscapedWhitespace(s string) string {
+ esc := strings.Split(s, "\\\n")
+ if len(esc) > 1 {
+ for i := 1; i < len(esc); i++ {
+ esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace)
+ }
+ }
+ return strings.Join(esc, "")
+}
+
+func (p *parser) replaceEscapes(str string) string {
+ var replaced []rune
+ s := []byte(str)
+ r := 0
+ for r < len(s) {
+ if s[r] != '\\' {
+ c, size := utf8.DecodeRune(s[r:])
+ r += size
+ replaced = append(replaced, c)
+ continue
+ }
+ r += 1
+ if r >= len(s) {
+ p.bug("Escape sequence at end of string.")
+ return ""
+ }
+ switch s[r] {
+ default:
+ p.bug("Expected valid escape code after \\, but got %q.", s[r])
+ return ""
+ case 'b':
+ replaced = append(replaced, rune(0x0008))
+ r += 1
+ case 't':
+ replaced = append(replaced, rune(0x0009))
+ r += 1
+ case 'n':
+ replaced = append(replaced, rune(0x000A))
+ r += 1
+ case 'f':
+ replaced = append(replaced, rune(0x000C))
+ r += 1
+ case 'r':
+ replaced = append(replaced, rune(0x000D))
+ r += 1
+ case '"':
+ replaced = append(replaced, rune(0x0022))
+ r += 1
+ case '\\':
+ replaced = append(replaced, rune(0x005C))
+ r += 1
+ case 'u':
+ // At this point, we know we have a Unicode escape of the form
+ // `uXXXX` at [r, r+5). (Because the lexer guarantees this
+ // for us.)
+ escaped := p.asciiEscapeToUnicode(s[r+1 : r+5])
+ replaced = append(replaced, escaped)
+ r += 5
+ case 'U':
+ // At this point, we know we have a Unicode escape of the form
+ // `uXXXX` at [r, r+9). (Because the lexer guarantees this
+ // for us.)
+ escaped := p.asciiEscapeToUnicode(s[r+1 : r+9])
+ replaced = append(replaced, escaped)
+ r += 9
+ }
+ }
+ return string(replaced)
+}
+
+func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
+ s := string(bs)
+ hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32)
+ if err != nil {
+ p.bug("Could not parse '%s' as a hexadecimal number, but the "+
+ "lexer claims it's OK: %s", s, err)
+ }
+ if !utf8.ValidRune(rune(hex)) {
+ p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
+ }
+ return rune(hex)
+}
+
+func isStringType(ty itemType) bool {
+ return ty == itemString || ty == itemMultilineString ||
+ ty == itemRawString || ty == itemRawMultilineString
+}
diff --git a/vendor/github.com/BurntSushi/toml/session.vim b/vendor/github.com/BurntSushi/toml/session.vim
new file mode 100644
index 0000000..562164b
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/session.vim
@@ -0,0 +1 @@
+au BufWritePost *.go silent!make tags > /dev/null 2>&1
diff --git a/vendor/github.com/BurntSushi/toml/type_check.go b/vendor/github.com/BurntSushi/toml/type_check.go
new file mode 100644
index 0000000..c73f8af
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/type_check.go
@@ -0,0 +1,91 @@
+package toml
+
+// tomlType represents any Go type that corresponds to a TOML type.
+// While the first draft of the TOML spec has a simplistic type system that
+// probably doesn't need this level of sophistication, we seem to be militating
+// toward adding real composite types.
+type tomlType interface {
+ typeString() string
+}
+
+// typeEqual accepts any two types and returns true if they are equal.
+func typeEqual(t1, t2 tomlType) bool {
+ if t1 == nil || t2 == nil {
+ return false
+ }
+ return t1.typeString() == t2.typeString()
+}
+
+func typeIsHash(t tomlType) bool {
+ return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash)
+}
+
+type tomlBaseType string
+
+func (btype tomlBaseType) typeString() string {
+ return string(btype)
+}
+
+func (btype tomlBaseType) String() string {
+ return btype.typeString()
+}
+
+var (
+ tomlInteger tomlBaseType = "Integer"
+ tomlFloat tomlBaseType = "Float"
+ tomlDatetime tomlBaseType = "Datetime"
+ tomlString tomlBaseType = "String"
+ tomlBool tomlBaseType = "Bool"
+ tomlArray tomlBaseType = "Array"
+ tomlHash tomlBaseType = "Hash"
+ tomlArrayHash tomlBaseType = "ArrayHash"
+)
+
+// typeOfPrimitive returns a tomlType of any primitive value in TOML.
+// Primitive values are: Integer, Float, Datetime, String and Bool.
+//
+// Passing a lexer item other than the following will cause a BUG message
+// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime.
+func (p *parser) typeOfPrimitive(lexItem item) tomlType {
+ switch lexItem.typ {
+ case itemInteger:
+ return tomlInteger
+ case itemFloat:
+ return tomlFloat
+ case itemDatetime:
+ return tomlDatetime
+ case itemString:
+ return tomlString
+ case itemMultilineString:
+ return tomlString
+ case itemRawString:
+ return tomlString
+ case itemRawMultilineString:
+ return tomlString
+ case itemBool:
+ return tomlBool
+ }
+ p.bug("Cannot infer primitive type of lex item '%s'.", lexItem)
+ panic("unreachable")
+}
+
+// typeOfArray returns a tomlType for an array given a list of types of its
+// values.
+//
+// In the current spec, if an array is homogeneous, then its type is always
+// "Array". If the array is not homogeneous, an error is generated.
+func (p *parser) typeOfArray(types []tomlType) tomlType {
+ // Empty arrays are cool.
+ if len(types) == 0 {
+ return tomlArray
+ }
+
+ theType := types[0]
+ for _, t := range types[1:] {
+ if !typeEqual(theType, t) {
+ p.panicf("Array contains values of type '%s' and '%s', but "+
+ "arrays must be homogeneous.", theType, t)
+ }
+ }
+ return tomlArray
+}
diff --git a/vendor/github.com/BurntSushi/toml/type_fields.go b/vendor/github.com/BurntSushi/toml/type_fields.go
new file mode 100644
index 0000000..608997c
--- /dev/null
+++ b/vendor/github.com/BurntSushi/toml/type_fields.go
@@ -0,0 +1,242 @@
+package toml
+
+// Struct field handling is adapted from code in encoding/json:
+//
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the Go distribution.
+
+import (
+ "reflect"
+ "sort"
+ "sync"
+)
+
+// A field represents a single field found in a struct.
+type field struct {
+ name string // the name of the field (`toml` tag included)
+ tag bool // whether field has a `toml` tag
+ index []int // represents the depth of an anonymous field
+ typ reflect.Type // the type of the field
+}
+
+// byName sorts field by name, breaking ties with depth,
+// then breaking ties with "name came from toml tag", then
+// breaking ties with index sequence.
+type byName []field
+
+func (x byName) Len() int { return len(x) }
+
+func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+
+func (x byName) Less(i, j int) bool {
+ if x[i].name != x[j].name {
+ return x[i].name < x[j].name
+ }
+ if len(x[i].index) != len(x[j].index) {
+ return len(x[i].index) < len(x[j].index)
+ }
+ if x[i].tag != x[j].tag {
+ return x[i].tag
+ }
+ return byIndex(x).Less(i, j)
+}
+
+// byIndex sorts field by index sequence.
+type byIndex []field
+
+func (x byIndex) Len() int { return len(x) }
+
+func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+
+func (x byIndex) Less(i, j int) bool {
+ for k, xik := range x[i].index {
+ if k >= len(x[j].index) {
+ return false
+ }
+ if xik != x[j].index[k] {
+ return xik < x[j].index[k]
+ }
+ }
+ return len(x[i].index) < len(x[j].index)
+}
+
+// typeFields returns a list of fields that TOML should recognize for the given
+// type. The algorithm is breadth-first search over the set of structs to
+// include - the top struct and then any reachable anonymous structs.
+func typeFields(t reflect.Type) []field {
+ // Anonymous fields to explore at the current level and the next.
+ current := []field{}
+ next := []field{{typ: t}}
+
+ // Count of queued names for current level and the next.
+ count := map[reflect.Type]int{}
+ nextCount := map[reflect.Type]int{}
+
+ // Types already visited at an earlier level.
+ visited := map[reflect.Type]bool{}
+
+ // Fields found.
+ var fields []field
+
+ for len(next) > 0 {
+ current, next = next, current[:0]
+ count, nextCount = nextCount, map[reflect.Type]int{}
+
+ for _, f := range current {
+ if visited[f.typ] {
+ continue
+ }
+ visited[f.typ] = true
+
+ // Scan f.typ for fields to include.
+ for i := 0; i < f.typ.NumField(); i++ {
+ sf := f.typ.Field(i)
+ if sf.PkgPath != "" && !sf.Anonymous { // unexported
+ continue
+ }
+ opts := getOptions(sf.Tag)
+ if opts.skip {
+ continue
+ }
+ index := make([]int, len(f.index)+1)
+ copy(index, f.index)
+ index[len(f.index)] = i
+
+ ft := sf.Type
+ if ft.Name() == "" && ft.Kind() == reflect.Ptr {
+ // Follow pointer.
+ ft = ft.Elem()
+ }
+
+ // Record found field and index sequence.
+ if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
+ tagged := opts.name != ""
+ name := opts.name
+ if name == "" {
+ name = sf.Name
+ }
+ fields = append(fields, field{name, tagged, index, ft})
+ if count[f.typ] > 1 {
+ // If there were multiple instances, add a second,
+ // so that the annihilation code will see a duplicate.
+ // It only cares about the distinction between 1 or 2,
+ // so don't bother generating any more copies.
+ fields = append(fields, fields[len(fields)-1])
+ }
+ continue
+ }
+
+ // Record new anonymous struct to explore in next round.
+ nextCount[ft]++
+ if nextCount[ft] == 1 {
+ f := field{name: ft.Name(), index: index, typ: ft}
+ next = append(next, f)
+ }
+ }
+ }
+ }
+
+ sort.Sort(byName(fields))
+
+ // Delete all fields that are hidden by the Go rules for embedded fields,
+ // except that fields with TOML tags are promoted.
+
+ // The fields are sorted in primary order of name, secondary order
+ // of field index length. Loop over names; for each name, delete
+ // hidden fields by choosing the one dominant field that survives.
+ out := fields[:0]
+ for advance, i := 0, 0; i < len(fields); i += advance {
+ // One iteration per name.
+ // Find the sequence of fields with the name of this first field.
+ fi := fields[i]
+ name := fi.name
+ for advance = 1; i+advance < len(fields); advance++ {
+ fj := fields[i+advance]
+ if fj.name != name {
+ break
+ }
+ }
+ if advance == 1 { // Only one field with this name
+ out = append(out, fi)
+ continue
+ }
+ dominant, ok := dominantField(fields[i : i+advance])
+ if ok {
+ out = append(out, dominant)
+ }
+ }
+
+ fields = out
+ sort.Sort(byIndex(fields))
+
+ return fields
+}
+
+// dominantField looks through the fields, all of which are known to
+// have the same name, to find the single field that dominates the
+// others using Go's embedding rules, modified by the presence of
+// TOML tags. If there are multiple top-level fields, the boolean
+// will be false: This condition is an error in Go and we skip all
+// the fields.
+func dominantField(fields []field) (field, bool) {
+ // The fields are sorted in increasing index-length order. The winner
+ // must therefore be one with the shortest index length. Drop all
+ // longer entries, which is easy: just truncate the slice.
+ length := len(fields[0].index)
+ tagged := -1 // Index of first tagged field.
+ for i, f := range fields {
+ if len(f.index) > length {
+ fields = fields[:i]
+ break
+ }
+ if f.tag {
+ if tagged >= 0 {
+ // Multiple tagged fields at the same level: conflict.
+ // Return no field.
+ return field{}, false
+ }
+ tagged = i
+ }
+ }
+ if tagged >= 0 {
+ return fields[tagged], true
+ }
+ // All remaining fields have the same length. If there's more than one,
+ // we have a conflict (two fields named "X" at the same level) and we
+ // return no field.
+ if len(fields) > 1 {
+ return field{}, false
+ }
+ return fields[0], true
+}
+
+var fieldCache struct {
+ sync.RWMutex
+ m map[reflect.Type][]field
+}
+
+// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
+func cachedTypeFields(t reflect.Type) []field {
+ fieldCache.RLock()
+ f := fieldCache.m[t]
+ fieldCache.RUnlock()
+ if f != nil {
+ return f
+ }
+
+ // Compute fields without lock.
+ // Might duplicate effort but won't hold other computations back.
+ f = typeFields(t)
+ if f == nil {
+ f = []field{}
+ }
+
+ fieldCache.Lock()
+ if fieldCache.m == nil {
+ fieldCache.m = map[reflect.Type][]field{}
+ }
+ fieldCache.m[t] = f
+ fieldCache.Unlock()
+ return f
+}
diff --git a/vendor/github.com/anaskhan96/soup/.gitignore b/vendor/github.com/anaskhan96/soup/.gitignore
new file mode 100644
index 0000000..9f11b75
--- /dev/null
+++ b/vendor/github.com/anaskhan96/soup/.gitignore
@@ -0,0 +1 @@
+.idea/
diff --git a/vendor/github.com/anaskhan96/soup/.travis.yml b/vendor/github.com/anaskhan96/soup/.travis.yml
new file mode 100644
index 0000000..0206c78
--- /dev/null
+++ b/vendor/github.com/anaskhan96/soup/.travis.yml
@@ -0,0 +1,9 @@
+language: go
+
+go:
+ - 1.6.x
+ - 1.7.x
+ - 1.8.x
+
+script:
+ - go test
diff --git a/vendor/github.com/anaskhan96/soup/CHANGELOG.md b/vendor/github.com/anaskhan96/soup/CHANGELOG.md
new file mode 100644
index 0000000..d9385ae
--- /dev/null
+++ b/vendor/github.com/anaskhan96/soup/CHANGELOG.md
@@ -0,0 +1,15 @@
+## v1.1
+
+### Added
+
+- Cookies can be added to the HTTP request, either via the `Cookies` map or the `Cookie()` function
+- Function `GetWithClient()` provides the ability to send the request with a custom HTTP client
+- Function `FindStrict()` finds the first instance of the mentioned tag with the exact matching values of the provided attribute (previously `Find()`)
+- Function `FindAllStrict()` finds all the instances of the mentioned tag with the exact matching values of the attributes (previously `FindAll()`)
+
+## Changed
+
+- Function `Find()` now finds the first instance of the mentioned tag with any matching values of the provided attribute.
+- Function `FindAll()` now finds all the instances of the mentioned tag with any matching values of the provided attribute.
+
+---
\ No newline at end of file
diff --git a/vendor/github.com/anaskhan96/soup/README.md b/vendor/github.com/anaskhan96/soup/README.md
new file mode 100644
index 0000000..b3027b7
--- /dev/null
+++ b/vendor/github.com/anaskhan96/soup/README.md
@@ -0,0 +1,70 @@
+# soup
+[![Build Status](https://travis-ci.org/anaskhan96/soup.svg?branch=master)](https://travis-ci.org/anaskhan96/soup)
+[![GoDoc](https://godoc.org/github.com/anaskhan96/soup?status.svg)](https://godoc.org/github.com/anaskhan96/soup)
+[![Go Report Card](https://goreportcard.com/badge/github.com/anaskhan96/soup)](https://goreportcard.com/report/github.com/anaskhan96/soup)
+
+**Web Scraper in Go, similar to BeautifulSoup**
+
+*soup* is a small web scraper package for Go, with its interface highly similar to that of BeautifulSoup.
+
+Exported variables and functions implemented till now :
+```go
+var Headers map[string]string // Set headers as a map of key-value pairs, an alternative to calling Header() individually
+var Cookies map[string]string // Set cookies as a map of key-value pairs, an alternative to calling Cookie() individually
+func Get(string) (string,error) // Takes the url as an argument, returns HTML string
+func GetWithClient(string, *http.Client) // Takes the url and a custom HTTP client as arguments, returns HTML string
+func Header(string, string) // Takes key,value pair to set as headers for the HTTP request made in Get()
+func Cookie(string, string) // Takes key, value pair to set as cookies to be sent with the HTTP request in Get()
+func HTMLParse(string) Root // Takes the HTML string as an argument, returns a pointer to the DOM constructed
+func Find([]string) Root // Element tag,(attribute key-value pair) as argument, pointer to first occurence returned
+func FindAll([]string) []Root // Same as Find(), but pointers to all occurrences returned
+func FindStrict([]string) Root // Element tag,(attribute key-value pair) as argument, pointer to first occurence returned with exact matching values
+func FindAllStrict([]string) []Root // Same as FindStrict(), but pointers to all occurrences returned
+func FindNextSibling() Root // Pointer to the next sibling of the Element in the DOM returned
+func FindNextElementSibling() Root // Pointer to the next element sibling of the Element in the DOM returned
+func FindPrevSibling() Root // Pointer to the previous sibling of the Element in the DOM returned
+func FindPrevElementSibling() Root // Pointer to the previous element sibling of the Element in the DOM returned
+func Attrs() map[string]string // Map returned with all the attributes of the Element as lookup to their respective values
+func Text() string // Full text inside a non-nested tag returned
+func SetDebug(bool) // Sets the debug mode to true or false; false by default
+```
+
+`Root` is a struct, containing three fields :
+* `Pointer` containing the pointer to the current html node
+* `NodeValue` containing the current html node's value, i.e. the tag name for an ElementNode, or the text in case of a TextNode
+* `Error` containing an error if one occurrs, else `nil` is returned.
+
+## Installation
+Install the package using the command
+```bash
+go get github.com/anaskhan96/soup
+```
+
+## Example
+An example code is given below to scrape the "Comics I Enjoy" part (text and its links) from [xkcd](https://xkcd.com).
+
+[More Examples](https://github.com/anaskhan96/soup/tree/master/examples)
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/anaskhan96/soup"
+ "os"
+)
+
+func main() {
+ resp, err := soup.Get("https://xkcd.com")
+ if err != nil {
+ os.Exit(1)
+ }
+ doc := soup.HTMLParse(resp)
+ links := doc.Find("div", "id", "comicLinks").FindAll("a")
+ for _, link := range links {
+ fmt.Println(link.Text(), "| Link :", link.Attrs()["href"])
+ }
+}
+```
+
+## Contributions
+This package was developed in my free time. However, contributions from everybody in the community are welcome, to make it a better web scraper. If you think there should be a particular feature or function included in the package, feel free to open up a new issue or pull request.
\ No newline at end of file
diff --git a/vendor/github.com/anaskhan96/soup/examples/weather/weather.go b/vendor/github.com/anaskhan96/soup/examples/weather/weather.go
new file mode 100644
index 0000000..9f9423a
--- /dev/null
+++ b/vendor/github.com/anaskhan96/soup/examples/weather/weather.go
@@ -0,0 +1,38 @@
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "log"
+ "os"
+ "strings"
+
+ "github.com/anaskhan96/soup"
+)
+
+func main() {
+ fmt.Printf("Enter the name of the city : ")
+ city, _ := bufio.NewReader(os.Stdin).ReadString('\n')
+ city = city[:len(city)-1]
+ cityInURL := strings.Join(strings.Split(city, " "), "+")
+ url := "https://www.bing.com/search?q=weather+" + cityInURL
+ resp, err := soup.Get(url)
+ if err != nil {
+ log.Fatal(err)
+ }
+ doc := soup.HTMLParse(resp)
+ grid := doc.Find("div", "class", "b_antiTopBleed b_antiSideBleed b_antiBottomBleed")
+ heading := grid.Find("div", "class", "wtr_titleCtrn").Find("div").Text()
+ conditions := grid.Find("div", "class", "wtr_condition")
+ primaryCondition := conditions.Find("div")
+ secondaryCondition := primaryCondition.FindNextElementSibling()
+ temp := primaryCondition.Find("div", "class", "wtr_condiTemp").Find("div").Text()
+ others := primaryCondition.Find("div", "class", "wtr_condiAttribs").FindAll("div")
+ caption := secondaryCondition.Find("div").Text()
+ fmt.Println("City Name : " + heading)
+ fmt.Println("Temperature : " + temp + "˚C")
+ for _, i := range others {
+ fmt.Println(i.Text())
+ }
+ fmt.Println(caption)
+}
diff --git a/vendor/github.com/anaskhan96/soup/examples/xkcdextract/xkcdextract.go b/vendor/github.com/anaskhan96/soup/examples/xkcdextract/xkcdextract.go
new file mode 100644
index 0000000..a81ff93
--- /dev/null
+++ b/vendor/github.com/anaskhan96/soup/examples/xkcdextract/xkcdextract.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/anaskhan96/soup"
+)
+
+func main() {
+ fmt.Println("Enter the xkcd comic number :")
+ var num int
+ fmt.Scanf("%d", &num)
+ url := fmt.Sprintf("https://xkcd.com/%d", num)
+ resp, _ := soup.Get(url)
+ doc := soup.HTMLParse(resp)
+ title := doc.Find("div", "id", "ctitle").Text()
+ fmt.Println("Title of the comic :", title)
+ comicImg := doc.Find("div", "id", "comic").Find("img")
+ fmt.Println("Source of the image :", comicImg.Attrs()["src"])
+ fmt.Println("Underlying text of the image :", comicImg.Attrs()["title"])
+}
+
+/* --- Console I/O ---
+Enter the xkcd comic number :
+353
+Title of the comic : Python
+Source of the image : //imgs.xkcd.com/comics/python.png
+Underlying text of the image : I wrote 20 short programs in Python yesterday. It was wonderful. Perl, I'm leaving you.
+*/
diff --git a/vendor/github.com/anaskhan96/soup/license b/vendor/github.com/anaskhan96/soup/license
new file mode 100644
index 0000000..c3ef1de
--- /dev/null
+++ b/vendor/github.com/anaskhan96/soup/license
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Anas Khan
+
+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.
diff --git a/vendor/github.com/anaskhan96/soup/soup.go b/vendor/github.com/anaskhan96/soup/soup.go
new file mode 100644
index 0000000..9e62ddf
--- /dev/null
+++ b/vendor/github.com/anaskhan96/soup/soup.go
@@ -0,0 +1,373 @@
+/* soup package implements a simple web scraper for Go,
+keeping it as similar as possible to BeautifulSoup
+*/
+
+package soup
+
+import (
+ "errors"
+ "io/ioutil"
+ "net/http"
+ "regexp"
+ "strings"
+
+ "golang.org/x/net/html"
+)
+
+// Root is a structure containing a pointer to an html node, the node value, and an error variable to return an error if occurred
+type Root struct {
+ Pointer *html.Node
+ NodeValue string
+ Error error
+}
+
+var debug = false
+
+// Headers contains all HTTP headers to send
+var Headers = make(map[string]string)
+
+// Cookies contains all HTTP cookies to send
+var Cookies = make(map[string]string)
+
+// SetDebug sets the debug status
+// Setting this to true causes the panics to be thrown and logged onto the console.
+// Setting this to false causes the errors to be saved in the Error field in the returned struct.
+func SetDebug(d bool) {
+ debug = d
+}
+
+// Header sets a new HTTP header
+func Header(n string, v string) {
+ Headers[n] = v
+}
+
+func Cookie(n string, v string) {
+ Cookies[n] = v
+}
+
+// GetWithClient returns the HTML returned by the url using a provided HTTP client
+func GetWithClient(url string, client *http.Client) (string, error) {
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ if debug {
+ panic("Couldn't perform GET request to " + url)
+ }
+ return "", errors.New("couldn't perform GET request to " + url)
+ }
+ // Set headers
+ for hName, hValue := range Headers {
+ req.Header.Set(hName, hValue)
+ }
+ // Set cookies
+ for cName, cValue := range Cookies {
+ req.AddCookie(&http.Cookie{
+ Name: cName,
+ Value: cValue,
+ })
+ }
+ // Perform request
+ resp, err := client.Do(req)
+ if err != nil {
+ if debug {
+ panic("Couldn't perform GET request to " + url)
+ }
+ return "", errors.New("couldn't perform GET request to " + url)
+ }
+ defer resp.Body.Close()
+ bytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ if debug {
+ panic("Unable to read the response body")
+ }
+ return "", errors.New("unable to read the response body")
+ }
+ return string(bytes), nil
+}
+
+// Get returns the HTML returned by the url in string using the default HTTP client
+func Get(url string) (string, error) {
+ // Init a new HTTP client
+ client := &http.Client{}
+ return GetWithClient(url, client)
+}
+
+// HTMLParse parses the HTML returning a start pointer to the DOM
+func HTMLParse(s string) Root {
+ r, err := html.Parse(strings.NewReader(s))
+ if err != nil {
+ if debug {
+ panic("Unable to parse the HTML")
+ }
+ return Root{nil, "", errors.New("unable to parse the HTML")}
+ }
+ for r.Type != html.ElementNode {
+ switch r.Type {
+ case html.DocumentNode:
+ r = r.FirstChild
+ case html.DoctypeNode:
+ r = r.NextSibling
+ case html.CommentNode:
+ r = r.NextSibling
+ }
+ }
+ return Root{r, r.Data, nil}
+}
+
+// Find finds the first occurrence of the given tag name,
+// with or without attribute key and value specified,
+// and returns a struct with a pointer to it
+func (r Root) Find(args ...string) Root {
+ temp, ok := findOnce(r.Pointer, args, false, false)
+ if ok == false {
+ if debug {
+ panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
+ }
+ return Root{nil, "", errors.New("element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")}
+ }
+ return Root{temp, temp.Data, nil}
+}
+
+// FindAll finds all occurrences of the given tag name,
+// with or without key and value specified,
+// and returns an array of structs, each having
+// the respective pointers
+func (r Root) FindAll(args ...string) []Root {
+ temp := findAllofem(r.Pointer, args, false)
+ if len(temp) == 0 {
+ if debug {
+ panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
+ }
+ return []Root{}
+ }
+ pointers := make([]Root, 0, len(temp))
+ for i := 0; i < len(temp); i++ {
+ pointers = append(pointers, Root{temp[i], temp[i].Data, nil})
+ }
+ return pointers
+}
+
+// FindStrict finds the first occurrence of the given tag name
+// only if all the values of the provided attribute are an exact match
+func (r Root) FindStrict(args ...string) Root {
+ temp, ok := findOnce(r.Pointer, args, false, true)
+ if ok == false {
+ if debug {
+ panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
+ }
+ return Root{nil, "", errors.New("element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")}
+ }
+ return Root{temp, temp.Data, nil}
+}
+
+// FindAllStrict finds all occurrences of the given tag name
+// only if all the values of the provided attribute are an exact match
+func (r Root) FindAllStrict(args ...string) []Root {
+ temp := findAllofem(r.Pointer, args, true)
+ if len(temp) == 0 {
+ if debug {
+ panic("Element `" + args[0] + "` with attributes `" + strings.Join(args[1:], " ") + "` not found")
+ }
+ return []Root{}
+ }
+ pointers := make([]Root, 0, len(temp))
+ for i := 0; i < len(temp); i++ {
+ pointers = append(pointers, Root{temp[i], temp[i].Data, nil})
+ }
+ return pointers
+}
+
+// FindNextSibling finds the next sibling of the pointer in the DOM
+// returning a struct with a pointer to it
+func (r Root) FindNextSibling() Root {
+ nextSibling := r.Pointer.NextSibling
+ if nextSibling == nil {
+ if debug {
+ panic("No next sibling found")
+ }
+ return Root{nil, "", errors.New("no next sibling found")}
+ }
+ return Root{nextSibling, nextSibling.Data, nil}
+}
+
+// FindPrevSibling finds the previous sibling of the pointer in the DOM
+// returning a struct with a pointer to it
+func (r Root) FindPrevSibling() Root {
+ prevSibling := r.Pointer.PrevSibling
+ if prevSibling == nil {
+ if debug {
+ panic("No previous sibling found")
+ }
+ return Root{nil, "", errors.New("no previous sibling found")}
+ }
+ return Root{prevSibling, prevSibling.Data, nil}
+}
+
+// FindNextElementSibling finds the next element sibling of the pointer in the DOM
+// returning a struct with a pointer to it
+func (r Root) FindNextElementSibling() Root {
+ nextSibling := r.Pointer.NextSibling
+ if nextSibling == nil {
+ if debug {
+ panic("No next element sibling found")
+ }
+ return Root{nil, "", errors.New("no next element sibling found")}
+ }
+ if nextSibling.Type == html.ElementNode {
+ return Root{nextSibling, nextSibling.Data, nil}
+ }
+ p := Root{nextSibling, nextSibling.Data, nil}
+ return p.FindNextElementSibling()
+}
+
+// FindPrevElementSibling finds the previous element sibling of the pointer in the DOM
+// returning a struct with a pointer to it
+func (r Root) FindPrevElementSibling() Root {
+ prevSibling := r.Pointer.PrevSibling
+ if prevSibling == nil {
+ if debug {
+ panic("No previous element sibling found")
+ }
+ return Root{nil, "", errors.New("no previous element sibling found")}
+ }
+ if prevSibling.Type == html.ElementNode {
+ return Root{prevSibling, prevSibling.Data, nil}
+ }
+ p := Root{prevSibling, prevSibling.Data, nil}
+ return p.FindPrevElementSibling()
+}
+
+// Attrs returns a map containing all attributes
+func (r Root) Attrs() map[string]string {
+ if r.Pointer.Type != html.ElementNode {
+ if debug {
+ panic("Not an ElementNode")
+ }
+ return nil
+ }
+ if len(r.Pointer.Attr) == 0 {
+ return nil
+ }
+ return getKeyValue(r.Pointer.Attr)
+}
+
+// Text returns the string inside a non-nested element
+func (r Root) Text() string {
+ k := r.Pointer.FirstChild
+checkNode:
+ if k.Type != html.TextNode {
+ k = k.NextSibling
+ if k == nil {
+ if debug {
+ panic("No text node found")
+ }
+ return ""
+ }
+ goto checkNode
+ }
+ if k != nil {
+ r, _ := regexp.Compile(`^\s+$`)
+ if ok := r.MatchString(k.Data); ok {
+ k = k.NextSibling
+ if k == nil {
+ if debug {
+ panic("No text node found")
+ }
+ return ""
+ }
+ goto checkNode
+ }
+ return k.Data
+ }
+ return ""
+}
+
+// Using depth first search to find the first occurrence and return
+func findOnce(n *html.Node, args []string, uni bool, strict bool) (*html.Node, bool) {
+ if uni == true {
+ if n.Type == html.ElementNode && n.Data == args[0] {
+ if len(args) > 1 && len(args) < 4 {
+ for i := 0; i < len(n.Attr); i++ {
+ attr := n.Attr[i]
+ searchAttrName := args[1]
+ searchAttrVal := args[2]
+ if (strict && attributeAndValueEquals(attr, searchAttrName, searchAttrVal)) ||
+ (!strict && attributeContainsValue(attr, searchAttrName, searchAttrVal)) {
+ return n, true
+ }
+ }
+ } else if len(args) == 1 {
+ return n, true
+ }
+ }
+ }
+ uni = true
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ p, q := findOnce(c, args, true, strict)
+ if q != false {
+ return p, q
+ }
+ }
+ return nil, false
+}
+
+// Using depth first search to find all occurrences and return
+func findAllofem(n *html.Node, args []string, strict bool) []*html.Node {
+ var nodeLinks = make([]*html.Node, 0, 10)
+ var f func(*html.Node, []string, bool)
+ f = func(n *html.Node, args []string, uni bool) {
+ if uni == true {
+ if n.Data == args[0] {
+ if len(args) > 1 && len(args) < 4 {
+ for i := 0; i < len(n.Attr); i++ {
+ attr := n.Attr[i]
+ searchAttrName := args[1]
+ searchAttrVal := args[2]
+ if (strict && attributeAndValueEquals(attr, searchAttrName, searchAttrVal)) ||
+ (!strict && attributeContainsValue(attr, searchAttrName, searchAttrVal)) {
+ nodeLinks = append(nodeLinks, n)
+ }
+ }
+ } else if len(args) == 1 {
+ nodeLinks = append(nodeLinks, n)
+ }
+ }
+ }
+ uni = true
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ f(c, args, true)
+ }
+ }
+ f(n, args, false)
+ return nodeLinks
+}
+
+// attributeAndValueEquals reports when the html.Attribute attr has the same attribute name and value as from
+// provided arguments
+func attributeAndValueEquals(attr html.Attribute, attribute, value string) bool {
+ return attr.Key == attribute && attr.Val == value
+}
+
+// attributeContainsValue reports when the html.Attribute attr has the same attribute name as from provided
+// attribute argument and compares if it has the same value in its values parameter
+func attributeContainsValue(attr html.Attribute, attribute, value string) bool {
+ if attr.Key == attribute {
+ for _, attrVal := range strings.Fields(attr.Val) {
+ if attrVal == value {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// Returns a key pair value (like a dictionary) for each attribute
+func getKeyValue(attributes []html.Attribute) map[string]string {
+ var keyvalues = make(map[string]string)
+ for i := 0; i < len(attributes); i++ {
+ _, exists := keyvalues[attributes[i].Key]
+ if exists == false {
+ keyvalues[attributes[i].Key] = attributes[i].Val
+ }
+ }
+ return keyvalues
+}
diff --git a/vendor/github.com/anaskhan96/soup/soup_test.go b/vendor/github.com/anaskhan96/soup/soup_test.go
new file mode 100644
index 0000000..41ef933
--- /dev/null
+++ b/vendor/github.com/anaskhan96/soup/soup_test.go
@@ -0,0 +1,169 @@
+package soup
+
+import (
+ "reflect"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+const testHTML = `
+
+
+ Sample "Hello, World" Application
+
+
+
+
+
+
+
+
+
+
Sample "Hello, World" Application
+
+
+
+
+
Just two divs peacing out
+
+ check
+
One more
+
This is the home page for the HelloWorld Web application.
+
To prove that they work, you can execute either of the following links:
+
+
+
+`
+
+var doc = HTMLParse(testHTML)
+var multipleClasses = HTMLParse(multipleClassesHTML)
+
+func TestFind(t *testing.T) {
+ // Find() and Attrs()
+ actual := doc.Find("img").Attrs()["src"]
+ if !reflect.DeepEqual(actual, "images/springsource.png") {
+ t.Error("Instead of `images/springsource.png`, got", actual)
+ }
+ // Find(...) and Text()
+ actual = doc.Find("a", "href", "hello").Text()
+ if !reflect.DeepEqual(actual, "servlet") {
+ t.Error("Instead of `servlet`, got", actual)
+ }
+ // Nested Find()
+ actual = doc.Find("div").Find("div").Text()
+ if !reflect.DeepEqual(actual, "Just two divs peacing out") {
+ t.Error("Instead of `Just two divs peacing out`, got", actual)
+ }
+}
+
+func TestFindNextPrevElement(t *testing.T) {
+ // FindNextSibling() and NodeValue field
+ actual := doc.Find("div", "id", "0").FindNextSibling().NodeValue
+ if !reflect.DeepEqual(strings.TrimSpace(actual), "check") {
+ t.Error("Instead of `check`, got", actual)
+ }
+ // FindPrevSibling() and NodeValue field
+ actual = doc.Find("div", "id", "2").FindPrevSibling().NodeValue
+ if !reflect.DeepEqual(strings.TrimSpace(actual), "check") {
+ t.Error("Instead of `check`, got", actual)
+ }
+ // FindNextElementSibling() and NodeValue field
+ actual = doc.Find("table").FindNextElementSibling().NodeValue
+ if !reflect.DeepEqual(actual, "div") {
+ t.Error("Instead of `div`, got", actual)
+ }
+ // FindPrevElementSibling() and NodeValue field
+ actual = doc.Find("p").FindPrevElementSibling().NodeValue
+ if !reflect.DeepEqual(actual, "div") {
+ t.Error("Instead of `div`, got", actual)
+ }
+}
+
+func TestFindAll(t *testing.T) {
+ // FindAll() and Attrs()
+ allDivs := doc.FindAll("div")
+ for i := 0; i < len(allDivs); i++ {
+ id := allDivs[i].Attrs()["id"]
+ actual, _ := strconv.Atoi(id)
+ if !reflect.DeepEqual(actual, i) {
+ t.Error("Instead of", i, "got", actual)
+ }
+ }
+}
+
+func TestFindAllBySingleClass(t *testing.T) {
+ actual := multipleClasses.FindAll("div", "class", "first")
+ if len(actual) != 6 {
+ t.Errorf("Expected 6 elements to be returned. Actual: %d", len(actual))
+ }
+ actual = multipleClasses.FindAll("div", "class", "third")
+ if len(actual) != 1 {
+ t.Errorf("Expected 1 element to be returned. Actual: %d", len(actual))
+ }
+}
+
+func TestFindBySingleClass(t *testing.T) {
+ actual := multipleClasses.Find("div", "class", "first")
+ if actual.Text() != "Multiple classes" {
+ t.Errorf("Wrong element returned: %s", actual.Text())
+ }
+ actual = multipleClasses.Find("div", "class", "third")
+ if actual.Text() != "Multiple classes inorder" {
+ t.Errorf("Wrong element returned: %s", actual.Text())
+ }
+}
+
+func TestFindAllStrict(t *testing.T) {
+ actual := multipleClasses.FindAllStrict("div", "class", "first second")
+ if len(actual) != 2 {
+ t.Errorf("Expected 2 elements to be returned. Actual: %d", len(actual))
+ }
+ actual = multipleClasses.FindAllStrict("div", "class", "first third second")
+ if len(actual) != 0 {
+ t.Errorf("0 elements should be returned")
+ }
+
+ actual = multipleClasses.FindAllStrict("div", "class", "second first third")
+ if len(actual) != 1 {
+ t.Errorf("Single item should be returned")
+ }
+}
+
+func TestFindStrict(t *testing.T) {
+ actual := multipleClasses.FindStrict("div", "class", "first")
+ if actual.Text() != "Single class" {
+ t.Errorf("Wrong element returned: %s", actual.Text())
+ }
+
+ actual = multipleClasses.FindStrict("div", "class", "third")
+ if actual.Error == nil {
+ t.Errorf("Element with class \"third\" should not be returned in strict mode")
+ }
+}
diff --git a/vendor/github.com/fatih/color/.travis.yml b/vendor/github.com/fatih/color/.travis.yml
new file mode 100644
index 0000000..95f8a1f
--- /dev/null
+++ b/vendor/github.com/fatih/color/.travis.yml
@@ -0,0 +1,5 @@
+language: go
+go:
+ - 1.8.x
+ - tip
+
diff --git a/vendor/github.com/fatih/color/Gopkg.lock b/vendor/github.com/fatih/color/Gopkg.lock
new file mode 100644
index 0000000..7d879e9
--- /dev/null
+++ b/vendor/github.com/fatih/color/Gopkg.lock
@@ -0,0 +1,27 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+ name = "github.com/mattn/go-colorable"
+ packages = ["."]
+ revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
+ version = "v0.0.9"
+
+[[projects]]
+ name = "github.com/mattn/go-isatty"
+ packages = ["."]
+ revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
+ version = "v0.0.3"
+
+[[projects]]
+ branch = "master"
+ name = "golang.org/x/sys"
+ packages = ["unix"]
+ revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd"
+
+[solve-meta]
+ analyzer-name = "dep"
+ analyzer-version = 1
+ inputs-digest = "e8a50671c3cb93ea935bf210b1cd20702876b9d9226129be581ef646d1565cdc"
+ solver-name = "gps-cdcl"
+ solver-version = 1
diff --git a/vendor/github.com/fatih/color/Gopkg.toml b/vendor/github.com/fatih/color/Gopkg.toml
new file mode 100644
index 0000000..ff1617f
--- /dev/null
+++ b/vendor/github.com/fatih/color/Gopkg.toml
@@ -0,0 +1,30 @@
+
+# Gopkg.toml example
+#
+# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+# name = "github.com/user/project"
+# version = "1.0.0"
+#
+# [[constraint]]
+# name = "github.com/user/project2"
+# branch = "dev"
+# source = "github.com/myfork/project2"
+#
+# [[override]]
+# name = "github.com/x/y"
+# version = "2.4.0"
+
+
+[[constraint]]
+ name = "github.com/mattn/go-colorable"
+ version = "0.0.9"
+
+[[constraint]]
+ name = "github.com/mattn/go-isatty"
+ version = "0.0.3"
diff --git a/vendor/github.com/fatih/color/LICENSE.md b/vendor/github.com/fatih/color/LICENSE.md
new file mode 100644
index 0000000..25fdaf6
--- /dev/null
+++ b/vendor/github.com/fatih/color/LICENSE.md
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Fatih Arslan
+
+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.
diff --git a/vendor/github.com/fatih/color/README.md b/vendor/github.com/fatih/color/README.md
new file mode 100644
index 0000000..3fc9544
--- /dev/null
+++ b/vendor/github.com/fatih/color/README.md
@@ -0,0 +1,179 @@
+# Color [![GoDoc](https://godoc.org/github.com/fatih/color?status.svg)](https://godoc.org/github.com/fatih/color) [![Build Status](https://img.shields.io/travis/fatih/color.svg?style=flat-square)](https://travis-ci.org/fatih/color)
+
+
+
+Color lets you use colorized outputs in terms of [ANSI Escape
+Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It
+has support for Windows too! The API can be used in several ways, pick one that
+suits you.
+
+
+![Color](https://i.imgur.com/c1JI0lA.png)
+
+
+## Install
+
+```bash
+go get github.com/fatih/color
+```
+
+Note that the `vendor` folder is here for stability. Remove the folder if you
+already have the dependencies in your GOPATH.
+
+## Examples
+
+### Standard colors
+
+```go
+// Print with default helper functions
+color.Cyan("Prints text in cyan.")
+
+// A newline will be appended automatically
+color.Blue("Prints %s in blue.", "text")
+
+// These are using the default foreground colors
+color.Red("We have red")
+color.Magenta("And many others ..")
+
+```
+
+### Mix and reuse colors
+
+```go
+// Create a new color object
+c := color.New(color.FgCyan).Add(color.Underline)
+c.Println("Prints cyan text with an underline.")
+
+// Or just add them to New()
+d := color.New(color.FgCyan, color.Bold)
+d.Printf("This prints bold cyan %s\n", "too!.")
+
+// Mix up foreground and background colors, create new mixes!
+red := color.New(color.FgRed)
+
+boldRed := red.Add(color.Bold)
+boldRed.Println("This will print text in bold red.")
+
+whiteBackground := red.Add(color.BgWhite)
+whiteBackground.Println("Red text with white background.")
+```
+
+### Use your own output (io.Writer)
+
+```go
+// Use your own io.Writer output
+color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
+
+blue := color.New(color.FgBlue)
+blue.Fprint(writer, "This will print text in blue.")
+```
+
+### Custom print functions (PrintFunc)
+
+```go
+// Create a custom print function for convenience
+red := color.New(color.FgRed).PrintfFunc()
+red("Warning")
+red("Error: %s", err)
+
+// Mix up multiple attributes
+notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
+notice("Don't forget this...")
+```
+
+### Custom fprint functions (FprintFunc)
+
+```go
+blue := color.New(FgBlue).FprintfFunc()
+blue(myWriter, "important notice: %s", stars)
+
+// Mix up with multiple attributes
+success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
+success(myWriter, "Don't forget this...")
+```
+
+### Insert into noncolor strings (SprintFunc)
+
+```go
+// Create SprintXxx functions to mix strings with other non-colorized strings:
+yellow := color.New(color.FgYellow).SprintFunc()
+red := color.New(color.FgRed).SprintFunc()
+fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error"))
+
+info := color.New(color.FgWhite, color.BgGreen).SprintFunc()
+fmt.Printf("This %s rocks!\n", info("package"))
+
+// Use helper functions
+fmt.Println("This", color.RedString("warning"), "should be not neglected.")
+fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.")
+
+// Windows supported too! Just don't forget to change the output to color.Output
+fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
+```
+
+### Plug into existing code
+
+```go
+// Use handy standard colors
+color.Set(color.FgYellow)
+
+fmt.Println("Existing text will now be in yellow")
+fmt.Printf("This one %s\n", "too")
+
+color.Unset() // Don't forget to unset
+
+// You can mix up parameters
+color.Set(color.FgMagenta, color.Bold)
+defer color.Unset() // Use it in your function
+
+fmt.Println("All text will now be bold magenta.")
+```
+
+### Disable/Enable color
+
+There might be a case where you want to explicitly disable/enable color output. the
+`go-isatty` package will automatically disable color output for non-tty output streams
+(for example if the output were piped directly to `less`)
+
+`Color` has support to disable/enable colors both globally and for single color
+definitions. For example suppose you have a CLI app and a `--no-color` bool flag. You
+can easily disable the color output with:
+
+```go
+
+var flagNoColor = flag.Bool("no-color", false, "Disable color output")
+
+if *flagNoColor {
+ color.NoColor = true // disables colorized output
+}
+```
+
+It also has support for single color definitions (local). You can
+disable/enable color output on the fly:
+
+```go
+c := color.New(color.FgCyan)
+c.Println("Prints cyan text")
+
+c.DisableColor()
+c.Println("This is printed without any color")
+
+c.EnableColor()
+c.Println("This prints again cyan...")
+```
+
+## Todo
+
+* Save/Return previous values
+* Evaluate fmt.Formatter interface
+
+
+## Credits
+
+ * [Fatih Arslan](https://github.com/fatih)
+ * Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable)
+
+## License
+
+The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details
+
diff --git a/vendor/github.com/fatih/color/color.go b/vendor/github.com/fatih/color/color.go
new file mode 100644
index 0000000..b1f591d
--- /dev/null
+++ b/vendor/github.com/fatih/color/color.go
@@ -0,0 +1,600 @@
+package color
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/mattn/go-colorable"
+ "github.com/mattn/go-isatty"
+)
+
+var (
+ // NoColor defines if the output is colorized or not. It's dynamically set to
+ // false or true based on the stdout's file descriptor referring to a terminal
+ // or not. This is a global option and affects all colors. For more control
+ // over each color block use the methods DisableColor() individually.
+ NoColor = os.Getenv("TERM") == "dumb" ||
+ (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
+
+ // Output defines the standard output of the print functions. By default
+ // os.Stdout is used.
+ Output = colorable.NewColorableStdout()
+
+ // colorsCache is used to reduce the count of created Color objects and
+ // allows to reuse already created objects with required Attribute.
+ colorsCache = make(map[Attribute]*Color)
+ colorsCacheMu sync.Mutex // protects colorsCache
+)
+
+// Color defines a custom color object which is defined by SGR parameters.
+type Color struct {
+ params []Attribute
+ noColor *bool
+}
+
+// Attribute defines a single SGR Code
+type Attribute int
+
+const escape = "\x1b"
+
+// Base attributes
+const (
+ Reset Attribute = iota
+ Bold
+ Faint
+ Italic
+ Underline
+ BlinkSlow
+ BlinkRapid
+ ReverseVideo
+ Concealed
+ CrossedOut
+)
+
+// Foreground text colors
+const (
+ FgBlack Attribute = iota + 30
+ FgRed
+ FgGreen
+ FgYellow
+ FgBlue
+ FgMagenta
+ FgCyan
+ FgWhite
+)
+
+// Foreground Hi-Intensity text colors
+const (
+ FgHiBlack Attribute = iota + 90
+ FgHiRed
+ FgHiGreen
+ FgHiYellow
+ FgHiBlue
+ FgHiMagenta
+ FgHiCyan
+ FgHiWhite
+)
+
+// Background text colors
+const (
+ BgBlack Attribute = iota + 40
+ BgRed
+ BgGreen
+ BgYellow
+ BgBlue
+ BgMagenta
+ BgCyan
+ BgWhite
+)
+
+// Background Hi-Intensity text colors
+const (
+ BgHiBlack Attribute = iota + 100
+ BgHiRed
+ BgHiGreen
+ BgHiYellow
+ BgHiBlue
+ BgHiMagenta
+ BgHiCyan
+ BgHiWhite
+)
+
+// New returns a newly created color object.
+func New(value ...Attribute) *Color {
+ c := &Color{params: make([]Attribute, 0)}
+ c.Add(value...)
+ return c
+}
+
+// Set sets the given parameters immediately. It will change the color of
+// output with the given SGR parameters until color.Unset() is called.
+func Set(p ...Attribute) *Color {
+ c := New(p...)
+ c.Set()
+ return c
+}
+
+// Unset resets all escape attributes and clears the output. Usually should
+// be called after Set().
+func Unset() {
+ if NoColor {
+ return
+ }
+
+ fmt.Fprintf(Output, "%s[%dm", escape, Reset)
+}
+
+// Set sets the SGR sequence.
+func (c *Color) Set() *Color {
+ if c.isNoColorSet() {
+ return c
+ }
+
+ fmt.Fprintf(Output, c.format())
+ return c
+}
+
+func (c *Color) unset() {
+ if c.isNoColorSet() {
+ return
+ }
+
+ Unset()
+}
+
+func (c *Color) setWriter(w io.Writer) *Color {
+ if c.isNoColorSet() {
+ return c
+ }
+
+ fmt.Fprintf(w, c.format())
+ return c
+}
+
+func (c *Color) unsetWriter(w io.Writer) {
+ if c.isNoColorSet() {
+ return
+ }
+
+ if NoColor {
+ return
+ }
+
+ fmt.Fprintf(w, "%s[%dm", escape, Reset)
+}
+
+// Add is used to chain SGR parameters. Use as many as parameters to combine
+// and create custom color objects. Example: Add(color.FgRed, color.Underline).
+func (c *Color) Add(value ...Attribute) *Color {
+ c.params = append(c.params, value...)
+ return c
+}
+
+func (c *Color) prepend(value Attribute) {
+ c.params = append(c.params, 0)
+ copy(c.params[1:], c.params[0:])
+ c.params[0] = value
+}
+
+// Fprint formats using the default formats for its operands and writes to w.
+// Spaces are added between operands when neither is a string.
+// It returns the number of bytes written and any write error encountered.
+// On Windows, users should wrap w with colorable.NewColorable() if w is of
+// type *os.File.
+func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
+ c.setWriter(w)
+ defer c.unsetWriter(w)
+
+ return fmt.Fprint(w, a...)
+}
+
+// Print formats using the default formats for its operands and writes to
+// standard output. Spaces are added between operands when neither is a
+// string. It returns the number of bytes written and any write error
+// encountered. This is the standard fmt.Print() method wrapped with the given
+// color.
+func (c *Color) Print(a ...interface{}) (n int, err error) {
+ c.Set()
+ defer c.unset()
+
+ return fmt.Fprint(Output, a...)
+}
+
+// Fprintf formats according to a format specifier and writes to w.
+// It returns the number of bytes written and any write error encountered.
+// On Windows, users should wrap w with colorable.NewColorable() if w is of
+// type *os.File.
+func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
+ c.setWriter(w)
+ defer c.unsetWriter(w)
+
+ return fmt.Fprintf(w, format, a...)
+}
+
+// Printf formats according to a format specifier and writes to standard output.
+// It returns the number of bytes written and any write error encountered.
+// This is the standard fmt.Printf() method wrapped with the given color.
+func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
+ c.Set()
+ defer c.unset()
+
+ return fmt.Fprintf(Output, format, a...)
+}
+
+// Fprintln formats using the default formats for its operands and writes to w.
+// Spaces are always added between operands and a newline is appended.
+// On Windows, users should wrap w with colorable.NewColorable() if w is of
+// type *os.File.
+func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
+ c.setWriter(w)
+ defer c.unsetWriter(w)
+
+ return fmt.Fprintln(w, a...)
+}
+
+// Println formats using the default formats for its operands and writes to
+// standard output. Spaces are always added between operands and a newline is
+// appended. It returns the number of bytes written and any write error
+// encountered. This is the standard fmt.Print() method wrapped with the given
+// color.
+func (c *Color) Println(a ...interface{}) (n int, err error) {
+ c.Set()
+ defer c.unset()
+
+ return fmt.Fprintln(Output, a...)
+}
+
+// Sprint is just like Print, but returns a string instead of printing it.
+func (c *Color) Sprint(a ...interface{}) string {
+ return c.wrap(fmt.Sprint(a...))
+}
+
+// Sprintln is just like Println, but returns a string instead of printing it.
+func (c *Color) Sprintln(a ...interface{}) string {
+ return c.wrap(fmt.Sprintln(a...))
+}
+
+// Sprintf is just like Printf, but returns a string instead of printing it.
+func (c *Color) Sprintf(format string, a ...interface{}) string {
+ return c.wrap(fmt.Sprintf(format, a...))
+}
+
+// FprintFunc returns a new function that prints the passed arguments as
+// colorized with color.Fprint().
+func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) {
+ return func(w io.Writer, a ...interface{}) {
+ c.Fprint(w, a...)
+ }
+}
+
+// PrintFunc returns a new function that prints the passed arguments as
+// colorized with color.Print().
+func (c *Color) PrintFunc() func(a ...interface{}) {
+ return func(a ...interface{}) {
+ c.Print(a...)
+ }
+}
+
+// FprintfFunc returns a new function that prints the passed arguments as
+// colorized with color.Fprintf().
+func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) {
+ return func(w io.Writer, format string, a ...interface{}) {
+ c.Fprintf(w, format, a...)
+ }
+}
+
+// PrintfFunc returns a new function that prints the passed arguments as
+// colorized with color.Printf().
+func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
+ return func(format string, a ...interface{}) {
+ c.Printf(format, a...)
+ }
+}
+
+// FprintlnFunc returns a new function that prints the passed arguments as
+// colorized with color.Fprintln().
+func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) {
+ return func(w io.Writer, a ...interface{}) {
+ c.Fprintln(w, a...)
+ }
+}
+
+// PrintlnFunc returns a new function that prints the passed arguments as
+// colorized with color.Println().
+func (c *Color) PrintlnFunc() func(a ...interface{}) {
+ return func(a ...interface{}) {
+ c.Println(a...)
+ }
+}
+
+// SprintFunc returns a new function that returns colorized strings for the
+// given arguments with fmt.Sprint(). Useful to put into or mix into other
+// string. Windows users should use this in conjunction with color.Output, example:
+//
+// put := New(FgYellow).SprintFunc()
+// fmt.Fprintf(color.Output, "This is a %s", put("warning"))
+func (c *Color) SprintFunc() func(a ...interface{}) string {
+ return func(a ...interface{}) string {
+ return c.wrap(fmt.Sprint(a...))
+ }
+}
+
+// SprintfFunc returns a new function that returns colorized strings for the
+// given arguments with fmt.Sprintf(). Useful to put into or mix into other
+// string. Windows users should use this in conjunction with color.Output.
+func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
+ return func(format string, a ...interface{}) string {
+ return c.wrap(fmt.Sprintf(format, a...))
+ }
+}
+
+// SprintlnFunc returns a new function that returns colorized strings for the
+// given arguments with fmt.Sprintln(). Useful to put into or mix into other
+// string. Windows users should use this in conjunction with color.Output.
+func (c *Color) SprintlnFunc() func(a ...interface{}) string {
+ return func(a ...interface{}) string {
+ return c.wrap(fmt.Sprintln(a...))
+ }
+}
+
+// sequence returns a formatted SGR sequence to be plugged into a "\x1b[...m"
+// an example output might be: "1;36" -> bold cyan
+func (c *Color) sequence() string {
+ format := make([]string, len(c.params))
+ for i, v := range c.params {
+ format[i] = strconv.Itoa(int(v))
+ }
+
+ return strings.Join(format, ";")
+}
+
+// wrap wraps the s string with the colors attributes. The string is ready to
+// be printed.
+func (c *Color) wrap(s string) string {
+ if c.isNoColorSet() {
+ return s
+ }
+
+ return c.format() + s + c.unformat()
+}
+
+func (c *Color) format() string {
+ return fmt.Sprintf("%s[%sm", escape, c.sequence())
+}
+
+func (c *Color) unformat() string {
+ return fmt.Sprintf("%s[%dm", escape, Reset)
+}
+
+// DisableColor disables the color output. Useful to not change any existing
+// code and still being able to output. Can be used for flags like
+// "--no-color". To enable back use EnableColor() method.
+func (c *Color) DisableColor() {
+ c.noColor = boolPtr(true)
+}
+
+// EnableColor enables the color output. Use it in conjunction with
+// DisableColor(). Otherwise this method has no side effects.
+func (c *Color) EnableColor() {
+ c.noColor = boolPtr(false)
+}
+
+func (c *Color) isNoColorSet() bool {
+ // check first if we have user setted action
+ if c.noColor != nil {
+ return *c.noColor
+ }
+
+ // if not return the global option, which is disabled by default
+ return NoColor
+}
+
+// Equals returns a boolean value indicating whether two colors are equal.
+func (c *Color) Equals(c2 *Color) bool {
+ if len(c.params) != len(c2.params) {
+ return false
+ }
+
+ for _, attr := range c.params {
+ if !c2.attrExists(attr) {
+ return false
+ }
+ }
+
+ return true
+}
+
+func (c *Color) attrExists(a Attribute) bool {
+ for _, attr := range c.params {
+ if attr == a {
+ return true
+ }
+ }
+
+ return false
+}
+
+func boolPtr(v bool) *bool {
+ return &v
+}
+
+func getCachedColor(p Attribute) *Color {
+ colorsCacheMu.Lock()
+ defer colorsCacheMu.Unlock()
+
+ c, ok := colorsCache[p]
+ if !ok {
+ c = New(p)
+ colorsCache[p] = c
+ }
+
+ return c
+}
+
+func colorPrint(format string, p Attribute, a ...interface{}) {
+ c := getCachedColor(p)
+
+ if !strings.HasSuffix(format, "\n") {
+ format += "\n"
+ }
+
+ if len(a) == 0 {
+ c.Print(format)
+ } else {
+ c.Printf(format, a...)
+ }
+}
+
+func colorString(format string, p Attribute, a ...interface{}) string {
+ c := getCachedColor(p)
+
+ if len(a) == 0 {
+ return c.SprintFunc()(format)
+ }
+
+ return c.SprintfFunc()(format, a...)
+}
+
+// Black is a convenient helper function to print with black foreground. A
+// newline is appended to format by default.
+func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }
+
+// Red is a convenient helper function to print with red foreground. A
+// newline is appended to format by default.
+func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }
+
+// Green is a convenient helper function to print with green foreground. A
+// newline is appended to format by default.
+func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }
+
+// Yellow is a convenient helper function to print with yellow foreground.
+// A newline is appended to format by default.
+func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }
+
+// Blue is a convenient helper function to print with blue foreground. A
+// newline is appended to format by default.
+func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }
+
+// Magenta is a convenient helper function to print with magenta foreground.
+// A newline is appended to format by default.
+func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }
+
+// Cyan is a convenient helper function to print with cyan foreground. A
+// newline is appended to format by default.
+func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }
+
+// White is a convenient helper function to print with white foreground. A
+// newline is appended to format by default.
+func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }
+
+// BlackString is a convenient helper function to return a string with black
+// foreground.
+func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }
+
+// RedString is a convenient helper function to return a string with red
+// foreground.
+func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }
+
+// GreenString is a convenient helper function to return a string with green
+// foreground.
+func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }
+
+// YellowString is a convenient helper function to return a string with yellow
+// foreground.
+func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }
+
+// BlueString is a convenient helper function to return a string with blue
+// foreground.
+func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }
+
+// MagentaString is a convenient helper function to return a string with magenta
+// foreground.
+func MagentaString(format string, a ...interface{}) string {
+ return colorString(format, FgMagenta, a...)
+}
+
+// CyanString is a convenient helper function to return a string with cyan
+// foreground.
+func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }
+
+// WhiteString is a convenient helper function to return a string with white
+// foreground.
+func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
+
+// HiBlack is a convenient helper function to print with hi-intensity black foreground. A
+// newline is appended to format by default.
+func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) }
+
+// HiRed is a convenient helper function to print with hi-intensity red foreground. A
+// newline is appended to format by default.
+func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) }
+
+// HiGreen is a convenient helper function to print with hi-intensity green foreground. A
+// newline is appended to format by default.
+func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) }
+
+// HiYellow is a convenient helper function to print with hi-intensity yellow foreground.
+// A newline is appended to format by default.
+func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) }
+
+// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A
+// newline is appended to format by default.
+func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) }
+
+// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground.
+// A newline is appended to format by default.
+func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) }
+
+// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A
+// newline is appended to format by default.
+func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) }
+
+// HiWhite is a convenient helper function to print with hi-intensity white foreground. A
+// newline is appended to format by default.
+func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) }
+
+// HiBlackString is a convenient helper function to return a string with hi-intensity black
+// foreground.
+func HiBlackString(format string, a ...interface{}) string {
+ return colorString(format, FgHiBlack, a...)
+}
+
+// HiRedString is a convenient helper function to return a string with hi-intensity red
+// foreground.
+func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) }
+
+// HiGreenString is a convenient helper function to return a string with hi-intensity green
+// foreground.
+func HiGreenString(format string, a ...interface{}) string {
+ return colorString(format, FgHiGreen, a...)
+}
+
+// HiYellowString is a convenient helper function to return a string with hi-intensity yellow
+// foreground.
+func HiYellowString(format string, a ...interface{}) string {
+ return colorString(format, FgHiYellow, a...)
+}
+
+// HiBlueString is a convenient helper function to return a string with hi-intensity blue
+// foreground.
+func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) }
+
+// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta
+// foreground.
+func HiMagentaString(format string, a ...interface{}) string {
+ return colorString(format, FgHiMagenta, a...)
+}
+
+// HiCyanString is a convenient helper function to return a string with hi-intensity cyan
+// foreground.
+func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) }
+
+// HiWhiteString is a convenient helper function to return a string with hi-intensity white
+// foreground.
+func HiWhiteString(format string, a ...interface{}) string {
+ return colorString(format, FgHiWhite, a...)
+}
diff --git a/vendor/github.com/fatih/color/color_test.go b/vendor/github.com/fatih/color/color_test.go
new file mode 100644
index 0000000..a8ed14f
--- /dev/null
+++ b/vendor/github.com/fatih/color/color_test.go
@@ -0,0 +1,342 @@
+package color
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/mattn/go-colorable"
+)
+
+// Testing colors is kinda different. First we test for given colors and their
+// escaped formatted results. Next we create some visual tests to be tested.
+// Each visual test includes the color name to be compared.
+func TestColor(t *testing.T) {
+ rb := new(bytes.Buffer)
+ Output = rb
+
+ NoColor = false
+
+ testColors := []struct {
+ text string
+ code Attribute
+ }{
+ {text: "black", code: FgBlack},
+ {text: "red", code: FgRed},
+ {text: "green", code: FgGreen},
+ {text: "yellow", code: FgYellow},
+ {text: "blue", code: FgBlue},
+ {text: "magent", code: FgMagenta},
+ {text: "cyan", code: FgCyan},
+ {text: "white", code: FgWhite},
+ {text: "hblack", code: FgHiBlack},
+ {text: "hred", code: FgHiRed},
+ {text: "hgreen", code: FgHiGreen},
+ {text: "hyellow", code: FgHiYellow},
+ {text: "hblue", code: FgHiBlue},
+ {text: "hmagent", code: FgHiMagenta},
+ {text: "hcyan", code: FgHiCyan},
+ {text: "hwhite", code: FgHiWhite},
+ }
+
+ for _, c := range testColors {
+ New(c.code).Print(c.text)
+
+ line, _ := rb.ReadString('\n')
+ scannedLine := fmt.Sprintf("%q", line)
+ colored := fmt.Sprintf("\x1b[%dm%s\x1b[0m", c.code, c.text)
+ escapedForm := fmt.Sprintf("%q", colored)
+
+ fmt.Printf("%s\t: %s\n", c.text, line)
+
+ if scannedLine != escapedForm {
+ t.Errorf("Expecting %s, got '%s'\n", escapedForm, scannedLine)
+ }
+ }
+
+ for _, c := range testColors {
+ line := New(c.code).Sprintf("%s", c.text)
+ scannedLine := fmt.Sprintf("%q", line)
+ colored := fmt.Sprintf("\x1b[%dm%s\x1b[0m", c.code, c.text)
+ escapedForm := fmt.Sprintf("%q", colored)
+
+ fmt.Printf("%s\t: %s\n", c.text, line)
+
+ if scannedLine != escapedForm {
+ t.Errorf("Expecting %s, got '%s'\n", escapedForm, scannedLine)
+ }
+ }
+}
+
+func TestColorEquals(t *testing.T) {
+ fgblack1 := New(FgBlack)
+ fgblack2 := New(FgBlack)
+ bgblack := New(BgBlack)
+ fgbgblack := New(FgBlack, BgBlack)
+ fgblackbgred := New(FgBlack, BgRed)
+ fgred := New(FgRed)
+ bgred := New(BgRed)
+
+ if !fgblack1.Equals(fgblack2) {
+ t.Error("Two black colors are not equal")
+ }
+
+ if fgblack1.Equals(bgblack) {
+ t.Error("Fg and bg black colors are equal")
+ }
+
+ if fgblack1.Equals(fgbgblack) {
+ t.Error("Fg black equals fg/bg black color")
+ }
+
+ if fgblack1.Equals(fgred) {
+ t.Error("Fg black equals Fg red")
+ }
+
+ if fgblack1.Equals(bgred) {
+ t.Error("Fg black equals Bg red")
+ }
+
+ if fgblack1.Equals(fgblackbgred) {
+ t.Error("Fg black equals fg black bg red")
+ }
+}
+
+func TestNoColor(t *testing.T) {
+ rb := new(bytes.Buffer)
+ Output = rb
+
+ testColors := []struct {
+ text string
+ code Attribute
+ }{
+ {text: "black", code: FgBlack},
+ {text: "red", code: FgRed},
+ {text: "green", code: FgGreen},
+ {text: "yellow", code: FgYellow},
+ {text: "blue", code: FgBlue},
+ {text: "magent", code: FgMagenta},
+ {text: "cyan", code: FgCyan},
+ {text: "white", code: FgWhite},
+ {text: "hblack", code: FgHiBlack},
+ {text: "hred", code: FgHiRed},
+ {text: "hgreen", code: FgHiGreen},
+ {text: "hyellow", code: FgHiYellow},
+ {text: "hblue", code: FgHiBlue},
+ {text: "hmagent", code: FgHiMagenta},
+ {text: "hcyan", code: FgHiCyan},
+ {text: "hwhite", code: FgHiWhite},
+ }
+
+ for _, c := range testColors {
+ p := New(c.code)
+ p.DisableColor()
+ p.Print(c.text)
+
+ line, _ := rb.ReadString('\n')
+ if line != c.text {
+ t.Errorf("Expecting %s, got '%s'\n", c.text, line)
+ }
+ }
+
+ // global check
+ NoColor = true
+ defer func() {
+ NoColor = false
+ }()
+ for _, c := range testColors {
+ p := New(c.code)
+ p.Print(c.text)
+
+ line, _ := rb.ReadString('\n')
+ if line != c.text {
+ t.Errorf("Expecting %s, got '%s'\n", c.text, line)
+ }
+ }
+
+}
+
+func TestColorVisual(t *testing.T) {
+ // First Visual Test
+ Output = colorable.NewColorableStdout()
+
+ New(FgRed).Printf("red\t")
+ New(BgRed).Print(" ")
+ New(FgRed, Bold).Println(" red")
+
+ New(FgGreen).Printf("green\t")
+ New(BgGreen).Print(" ")
+ New(FgGreen, Bold).Println(" green")
+
+ New(FgYellow).Printf("yellow\t")
+ New(BgYellow).Print(" ")
+ New(FgYellow, Bold).Println(" yellow")
+
+ New(FgBlue).Printf("blue\t")
+ New(BgBlue).Print(" ")
+ New(FgBlue, Bold).Println(" blue")
+
+ New(FgMagenta).Printf("magenta\t")
+ New(BgMagenta).Print(" ")
+ New(FgMagenta, Bold).Println(" magenta")
+
+ New(FgCyan).Printf("cyan\t")
+ New(BgCyan).Print(" ")
+ New(FgCyan, Bold).Println(" cyan")
+
+ New(FgWhite).Printf("white\t")
+ New(BgWhite).Print(" ")
+ New(FgWhite, Bold).Println(" white")
+ fmt.Println("")
+
+ // Second Visual test
+ Black("black")
+ Red("red")
+ Green("green")
+ Yellow("yellow")
+ Blue("blue")
+ Magenta("magenta")
+ Cyan("cyan")
+ White("white")
+ HiBlack("hblack")
+ HiRed("hred")
+ HiGreen("hgreen")
+ HiYellow("hyellow")
+ HiBlue("hblue")
+ HiMagenta("hmagenta")
+ HiCyan("hcyan")
+ HiWhite("hwhite")
+
+ // Third visual test
+ fmt.Println()
+ Set(FgBlue)
+ fmt.Println("is this blue?")
+ Unset()
+
+ Set(FgMagenta)
+ fmt.Println("and this magenta?")
+ Unset()
+
+ // Fourth Visual test
+ fmt.Println()
+ blue := New(FgBlue).PrintlnFunc()
+ blue("blue text with custom print func")
+
+ red := New(FgRed).PrintfFunc()
+ red("red text with a printf func: %d\n", 123)
+
+ put := New(FgYellow).SprintFunc()
+ warn := New(FgRed).SprintFunc()
+
+ fmt.Fprintf(Output, "this is a %s and this is %s.\n", put("warning"), warn("error"))
+
+ info := New(FgWhite, BgGreen).SprintFunc()
+ fmt.Fprintf(Output, "this %s rocks!\n", info("package"))
+
+ notice := New(FgBlue).FprintFunc()
+ notice(os.Stderr, "just a blue notice to stderr")
+
+ // Fifth Visual Test
+ fmt.Println()
+
+ fmt.Fprintln(Output, BlackString("black"))
+ fmt.Fprintln(Output, RedString("red"))
+ fmt.Fprintln(Output, GreenString("green"))
+ fmt.Fprintln(Output, YellowString("yellow"))
+ fmt.Fprintln(Output, BlueString("blue"))
+ fmt.Fprintln(Output, MagentaString("magenta"))
+ fmt.Fprintln(Output, CyanString("cyan"))
+ fmt.Fprintln(Output, WhiteString("white"))
+ fmt.Fprintln(Output, HiBlackString("hblack"))
+ fmt.Fprintln(Output, HiRedString("hred"))
+ fmt.Fprintln(Output, HiGreenString("hgreen"))
+ fmt.Fprintln(Output, HiYellowString("hyellow"))
+ fmt.Fprintln(Output, HiBlueString("hblue"))
+ fmt.Fprintln(Output, HiMagentaString("hmagenta"))
+ fmt.Fprintln(Output, HiCyanString("hcyan"))
+ fmt.Fprintln(Output, HiWhiteString("hwhite"))
+}
+
+func TestNoFormat(t *testing.T) {
+ fmt.Printf("%s %%s = ", BlackString("Black"))
+ Black("%s")
+
+ fmt.Printf("%s %%s = ", RedString("Red"))
+ Red("%s")
+
+ fmt.Printf("%s %%s = ", GreenString("Green"))
+ Green("%s")
+
+ fmt.Printf("%s %%s = ", YellowString("Yellow"))
+ Yellow("%s")
+
+ fmt.Printf("%s %%s = ", BlueString("Blue"))
+ Blue("%s")
+
+ fmt.Printf("%s %%s = ", MagentaString("Magenta"))
+ Magenta("%s")
+
+ fmt.Printf("%s %%s = ", CyanString("Cyan"))
+ Cyan("%s")
+
+ fmt.Printf("%s %%s = ", WhiteString("White"))
+ White("%s")
+
+ fmt.Printf("%s %%s = ", HiBlackString("HiBlack"))
+ HiBlack("%s")
+
+ fmt.Printf("%s %%s = ", HiRedString("HiRed"))
+ HiRed("%s")
+
+ fmt.Printf("%s %%s = ", HiGreenString("HiGreen"))
+ HiGreen("%s")
+
+ fmt.Printf("%s %%s = ", HiYellowString("HiYellow"))
+ HiYellow("%s")
+
+ fmt.Printf("%s %%s = ", HiBlueString("HiBlue"))
+ HiBlue("%s")
+
+ fmt.Printf("%s %%s = ", HiMagentaString("HiMagenta"))
+ HiMagenta("%s")
+
+ fmt.Printf("%s %%s = ", HiCyanString("HiCyan"))
+ HiCyan("%s")
+
+ fmt.Printf("%s %%s = ", HiWhiteString("HiWhite"))
+ HiWhite("%s")
+}
+
+func TestNoFormatString(t *testing.T) {
+ tests := []struct {
+ f func(string, ...interface{}) string
+ format string
+ args []interface{}
+ want string
+ }{
+ {BlackString, "%s", nil, "\x1b[30m%s\x1b[0m"},
+ {RedString, "%s", nil, "\x1b[31m%s\x1b[0m"},
+ {GreenString, "%s", nil, "\x1b[32m%s\x1b[0m"},
+ {YellowString, "%s", nil, "\x1b[33m%s\x1b[0m"},
+ {BlueString, "%s", nil, "\x1b[34m%s\x1b[0m"},
+ {MagentaString, "%s", nil, "\x1b[35m%s\x1b[0m"},
+ {CyanString, "%s", nil, "\x1b[36m%s\x1b[0m"},
+ {WhiteString, "%s", nil, "\x1b[37m%s\x1b[0m"},
+ {HiBlackString, "%s", nil, "\x1b[90m%s\x1b[0m"},
+ {HiRedString, "%s", nil, "\x1b[91m%s\x1b[0m"},
+ {HiGreenString, "%s", nil, "\x1b[92m%s\x1b[0m"},
+ {HiYellowString, "%s", nil, "\x1b[93m%s\x1b[0m"},
+ {HiBlueString, "%s", nil, "\x1b[94m%s\x1b[0m"},
+ {HiMagentaString, "%s", nil, "\x1b[95m%s\x1b[0m"},
+ {HiCyanString, "%s", nil, "\x1b[96m%s\x1b[0m"},
+ {HiWhiteString, "%s", nil, "\x1b[97m%s\x1b[0m"},
+ }
+
+ for i, test := range tests {
+ s := fmt.Sprintf("%s", test.f(test.format, test.args...))
+ if s != test.want {
+ t.Errorf("[%d] want: %q, got: %q", i, test.want, s)
+ }
+ }
+}
diff --git a/vendor/github.com/fatih/color/doc.go b/vendor/github.com/fatih/color/doc.go
new file mode 100644
index 0000000..cf1e965
--- /dev/null
+++ b/vendor/github.com/fatih/color/doc.go
@@ -0,0 +1,133 @@
+/*
+Package color is an ANSI color package to output colorized or SGR defined
+output to the standard output. The API can be used in several way, pick one
+that suits you.
+
+Use simple and default helper functions with predefined foreground colors:
+
+ color.Cyan("Prints text in cyan.")
+
+ // a newline will be appended automatically
+ color.Blue("Prints %s in blue.", "text")
+
+ // More default foreground colors..
+ color.Red("We have red")
+ color.Yellow("Yellow color too!")
+ color.Magenta("And many others ..")
+
+ // Hi-intensity colors
+ color.HiGreen("Bright green color.")
+ color.HiBlack("Bright black means gray..")
+ color.HiWhite("Shiny white color!")
+
+However there are times where custom color mixes are required. Below are some
+examples to create custom color objects and use the print functions of each
+separate color object.
+
+ // Create a new color object
+ c := color.New(color.FgCyan).Add(color.Underline)
+ c.Println("Prints cyan text with an underline.")
+
+ // Or just add them to New()
+ d := color.New(color.FgCyan, color.Bold)
+ d.Printf("This prints bold cyan %s\n", "too!.")
+
+
+ // Mix up foreground and background colors, create new mixes!
+ red := color.New(color.FgRed)
+
+ boldRed := red.Add(color.Bold)
+ boldRed.Println("This will print text in bold red.")
+
+ whiteBackground := red.Add(color.BgWhite)
+ whiteBackground.Println("Red text with White background.")
+
+ // Use your own io.Writer output
+ color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
+
+ blue := color.New(color.FgBlue)
+ blue.Fprint(myWriter, "This will print text in blue.")
+
+You can create PrintXxx functions to simplify even more:
+
+ // Create a custom print function for convenient
+ red := color.New(color.FgRed).PrintfFunc()
+ red("warning")
+ red("error: %s", err)
+
+ // Mix up multiple attributes
+ notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
+ notice("don't forget this...")
+
+You can also FprintXxx functions to pass your own io.Writer:
+
+ blue := color.New(FgBlue).FprintfFunc()
+ blue(myWriter, "important notice: %s", stars)
+
+ // Mix up with multiple attributes
+ success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
+ success(myWriter, don't forget this...")
+
+
+Or create SprintXxx functions to mix strings with other non-colorized strings:
+
+ yellow := New(FgYellow).SprintFunc()
+ red := New(FgRed).SprintFunc()
+
+ fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error"))
+
+ info := New(FgWhite, BgGreen).SprintFunc()
+ fmt.Printf("this %s rocks!\n", info("package"))
+
+Windows support is enabled by default. All Print functions work as intended.
+However only for color.SprintXXX functions, user should use fmt.FprintXXX and
+set the output to color.Output:
+
+ fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
+
+ info := New(FgWhite, BgGreen).SprintFunc()
+ fmt.Fprintf(color.Output, "this %s rocks!\n", info("package"))
+
+Using with existing code is possible. Just use the Set() method to set the
+standard output to the given parameters. That way a rewrite of an existing
+code is not required.
+
+ // Use handy standard colors.
+ color.Set(color.FgYellow)
+
+ fmt.Println("Existing text will be now in Yellow")
+ fmt.Printf("This one %s\n", "too")
+
+ color.Unset() // don't forget to unset
+
+ // You can mix up parameters
+ color.Set(color.FgMagenta, color.Bold)
+ defer color.Unset() // use it in your function
+
+ fmt.Println("All text will be now bold magenta.")
+
+There might be a case where you want to disable color output (for example to
+pipe the standard output of your app to somewhere else). `Color` has support to
+disable colors both globally and for single color definition. For example
+suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
+the color output with:
+
+ var flagNoColor = flag.Bool("no-color", false, "Disable color output")
+
+ if *flagNoColor {
+ color.NoColor = true // disables colorized output
+ }
+
+It also has support for single color definitions (local). You can
+disable/enable color output on the fly:
+
+ c := color.New(color.FgCyan)
+ c.Println("Prints cyan text")
+
+ c.DisableColor()
+ c.Println("This is printed without any color")
+
+ c.EnableColor()
+ c.Println("This prints again cyan...")
+*/
+package color
diff --git a/vendor/github.com/mattn/go-colorable/.travis.yml b/vendor/github.com/mattn/go-colorable/.travis.yml
new file mode 100644
index 0000000..98db8f0
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/.travis.yml
@@ -0,0 +1,9 @@
+language: go
+go:
+ - tip
+
+before_install:
+ - go get github.com/mattn/goveralls
+ - go get golang.org/x/tools/cmd/cover
+script:
+ - $HOME/gopath/bin/goveralls -repotoken xnXqRGwgW3SXIguzxf90ZSK1GPYZPaGrw
diff --git a/vendor/github.com/mattn/go-colorable/LICENSE b/vendor/github.com/mattn/go-colorable/LICENSE
new file mode 100644
index 0000000..91b5cef
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Yasuhiro Matsumoto
+
+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.
diff --git a/vendor/github.com/mattn/go-colorable/README.md b/vendor/github.com/mattn/go-colorable/README.md
new file mode 100644
index 0000000..56729a9
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/README.md
@@ -0,0 +1,48 @@
+# go-colorable
+
+[![Godoc Reference](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable)
+[![Build Status](https://travis-ci.org/mattn/go-colorable.svg?branch=master)](https://travis-ci.org/mattn/go-colorable)
+[![Coverage Status](https://coveralls.io/repos/github/mattn/go-colorable/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-colorable?branch=master)
+[![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable)
+
+Colorable writer for windows.
+
+For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.)
+This package is possible to handle escape sequence for ansi color on windows.
+
+## Too Bad!
+
+![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png)
+
+
+## So Good!
+
+![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png)
+
+## Usage
+
+```go
+logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
+logrus.SetOutput(colorable.NewColorableStdout())
+
+logrus.Info("succeeded")
+logrus.Warn("not correct")
+logrus.Error("something error")
+logrus.Fatal("panic")
+```
+
+You can compile above code on non-windows OSs.
+
+## Installation
+
+```
+$ go get github.com/mattn/go-colorable
+```
+
+# License
+
+MIT
+
+# Author
+
+Yasuhiro Matsumoto (a.k.a mattn)
diff --git a/vendor/github.com/mattn/go-colorable/_example/escape-seq/main.go b/vendor/github.com/mattn/go-colorable/_example/escape-seq/main.go
new file mode 100644
index 0000000..8cbcb90
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/_example/escape-seq/main.go
@@ -0,0 +1,16 @@
+package main
+
+import (
+ "bufio"
+ "fmt"
+
+ "github.com/mattn/go-colorable"
+)
+
+func main() {
+ stdOut := bufio.NewWriter(colorable.NewColorableStdout())
+
+ fmt.Fprint(stdOut, "\x1B[3GMove to 3rd Column\n")
+ fmt.Fprint(stdOut, "\x1B[1;2HMove to 2nd Column on 1st Line\n")
+ stdOut.Flush()
+}
diff --git a/vendor/github.com/mattn/go-colorable/_example/logrus/main.go b/vendor/github.com/mattn/go-colorable/_example/logrus/main.go
new file mode 100644
index 0000000..c569164
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/_example/logrus/main.go
@@ -0,0 +1,16 @@
+package main
+
+import (
+ "github.com/mattn/go-colorable"
+ "github.com/sirupsen/logrus"
+)
+
+func main() {
+ logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
+ logrus.SetOutput(colorable.NewColorableStdout())
+
+ logrus.Info("succeeded")
+ logrus.Warn("not correct")
+ logrus.Error("something error")
+ logrus.Fatal("panic")
+}
diff --git a/vendor/github.com/mattn/go-colorable/_example/title/main.go b/vendor/github.com/mattn/go-colorable/_example/title/main.go
new file mode 100644
index 0000000..e208870
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/_example/title/main.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ . "github.com/mattn/go-colorable"
+)
+
+func main() {
+ out := NewColorableStdout()
+ fmt.Fprint(out, "\x1B]0;TITLE Changed\007(See title and hit any key)")
+ var c [1]byte
+ os.Stdin.Read(c[:])
+}
diff --git a/vendor/github.com/mattn/go-colorable/colorable_appengine.go b/vendor/github.com/mattn/go-colorable/colorable_appengine.go
new file mode 100644
index 0000000..1f28d77
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/colorable_appengine.go
@@ -0,0 +1,29 @@
+// +build appengine
+
+package colorable
+
+import (
+ "io"
+ "os"
+
+ _ "github.com/mattn/go-isatty"
+)
+
+// NewColorable return new instance of Writer which handle escape sequence.
+func NewColorable(file *os.File) io.Writer {
+ if file == nil {
+ panic("nil passed instead of *os.File to NewColorable()")
+ }
+
+ return file
+}
+
+// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
+func NewColorableStdout() io.Writer {
+ return os.Stdout
+}
+
+// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
+func NewColorableStderr() io.Writer {
+ return os.Stderr
+}
diff --git a/vendor/github.com/mattn/go-colorable/colorable_others.go b/vendor/github.com/mattn/go-colorable/colorable_others.go
new file mode 100644
index 0000000..887f203
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/colorable_others.go
@@ -0,0 +1,30 @@
+// +build !windows
+// +build !appengine
+
+package colorable
+
+import (
+ "io"
+ "os"
+
+ _ "github.com/mattn/go-isatty"
+)
+
+// NewColorable return new instance of Writer which handle escape sequence.
+func NewColorable(file *os.File) io.Writer {
+ if file == nil {
+ panic("nil passed instead of *os.File to NewColorable()")
+ }
+
+ return file
+}
+
+// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
+func NewColorableStdout() io.Writer {
+ return os.Stdout
+}
+
+// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
+func NewColorableStderr() io.Writer {
+ return os.Stderr
+}
diff --git a/vendor/github.com/mattn/go-colorable/colorable_test.go b/vendor/github.com/mattn/go-colorable/colorable_test.go
new file mode 100644
index 0000000..3069869
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/colorable_test.go
@@ -0,0 +1,83 @@
+package colorable
+
+import (
+ "bytes"
+ "os"
+ "runtime"
+ "testing"
+)
+
+// checkEncoding checks that colorable is output encoding agnostic as long as
+// the encoding is a superset of ASCII. This implies that one byte not part of
+// an ANSI sequence must give exactly one byte in output
+func checkEncoding(t *testing.T, data []byte) {
+ // Send non-UTF8 data to colorable
+ b := bytes.NewBuffer(make([]byte, 0, 10))
+ if b.Len() != 0 {
+ t.FailNow()
+ }
+ // TODO move colorable wrapping outside the test
+ c := NewNonColorable(b)
+ c.Write(data)
+ if b.Len() != len(data) {
+ t.Fatalf("%d bytes expected, got %d", len(data), b.Len())
+ }
+}
+
+func TestEncoding(t *testing.T) {
+ checkEncoding(t, []byte{}) // Empty
+ checkEncoding(t, []byte(`abc`)) // "abc"
+ checkEncoding(t, []byte(`é`)) // "é" in UTF-8
+ checkEncoding(t, []byte{233}) // 'é' in Latin-1
+}
+
+func TestNonColorable(t *testing.T) {
+ var buf bytes.Buffer
+ want := "hello"
+ NewNonColorable(&buf).Write([]byte("\x1b[0m" + want + "\x1b[2J"))
+ got := buf.String()
+ if got != "hello" {
+ t.Fatalf("want %q but %q", want, got)
+ }
+
+ buf.Reset()
+ NewNonColorable(&buf).Write([]byte("\x1b["))
+ got = buf.String()
+ if got != "" {
+ t.Fatalf("want %q but %q", "", got)
+ }
+}
+
+func TestNonColorableNil(t *testing.T) {
+ paniced := false
+ func() {
+ defer func() {
+ recover()
+ paniced = true
+ }()
+ NewNonColorable(nil)
+ NewColorable(nil)
+ }()
+
+ if !paniced {
+ t.Fatalf("should panic")
+ }
+}
+
+func TestColorable(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skipf("skip this test on windows")
+ }
+ _, ok := NewColorableStdout().(*os.File)
+ if !ok {
+ t.Fatalf("should os.Stdout on UNIX")
+ }
+ _, ok = NewColorableStderr().(*os.File)
+ if !ok {
+ t.Fatalf("should os.Stdout on UNIX")
+ }
+ _, ok = NewColorable(os.Stdout).(*os.File)
+ if !ok {
+ t.Fatalf("should os.Stdout on UNIX")
+ }
+}
diff --git a/vendor/github.com/mattn/go-colorable/colorable_windows.go b/vendor/github.com/mattn/go-colorable/colorable_windows.go
new file mode 100644
index 0000000..e17a547
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/colorable_windows.go
@@ -0,0 +1,884 @@
+// +build windows
+// +build !appengine
+
+package colorable
+
+import (
+ "bytes"
+ "io"
+ "math"
+ "os"
+ "strconv"
+ "strings"
+ "syscall"
+ "unsafe"
+
+ "github.com/mattn/go-isatty"
+)
+
+const (
+ foregroundBlue = 0x1
+ foregroundGreen = 0x2
+ foregroundRed = 0x4
+ foregroundIntensity = 0x8
+ foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
+ backgroundBlue = 0x10
+ backgroundGreen = 0x20
+ backgroundRed = 0x40
+ backgroundIntensity = 0x80
+ backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
+)
+
+type wchar uint16
+type short int16
+type dword uint32
+type word uint16
+
+type coord struct {
+ x short
+ y short
+}
+
+type smallRect struct {
+ left short
+ top short
+ right short
+ bottom short
+}
+
+type consoleScreenBufferInfo struct {
+ size coord
+ cursorPosition coord
+ attributes word
+ window smallRect
+ maximumWindowSize coord
+}
+
+type consoleCursorInfo struct {
+ size dword
+ visible int32
+}
+
+var (
+ kernel32 = syscall.NewLazyDLL("kernel32.dll")
+ procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
+ procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
+ procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
+ procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
+ procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
+ procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
+ procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
+ procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW")
+)
+
+// Writer provide colorable Writer to the console
+type Writer struct {
+ out io.Writer
+ handle syscall.Handle
+ oldattr word
+ oldpos coord
+}
+
+// NewColorable return new instance of Writer which handle escape sequence from File.
+func NewColorable(file *os.File) io.Writer {
+ if file == nil {
+ panic("nil passed instead of *os.File to NewColorable()")
+ }
+
+ if isatty.IsTerminal(file.Fd()) {
+ var csbi consoleScreenBufferInfo
+ handle := syscall.Handle(file.Fd())
+ procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+ return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}}
+ }
+ return file
+}
+
+// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
+func NewColorableStdout() io.Writer {
+ return NewColorable(os.Stdout)
+}
+
+// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
+func NewColorableStderr() io.Writer {
+ return NewColorable(os.Stderr)
+}
+
+var color256 = map[int]int{
+ 0: 0x000000,
+ 1: 0x800000,
+ 2: 0x008000,
+ 3: 0x808000,
+ 4: 0x000080,
+ 5: 0x800080,
+ 6: 0x008080,
+ 7: 0xc0c0c0,
+ 8: 0x808080,
+ 9: 0xff0000,
+ 10: 0x00ff00,
+ 11: 0xffff00,
+ 12: 0x0000ff,
+ 13: 0xff00ff,
+ 14: 0x00ffff,
+ 15: 0xffffff,
+ 16: 0x000000,
+ 17: 0x00005f,
+ 18: 0x000087,
+ 19: 0x0000af,
+ 20: 0x0000d7,
+ 21: 0x0000ff,
+ 22: 0x005f00,
+ 23: 0x005f5f,
+ 24: 0x005f87,
+ 25: 0x005faf,
+ 26: 0x005fd7,
+ 27: 0x005fff,
+ 28: 0x008700,
+ 29: 0x00875f,
+ 30: 0x008787,
+ 31: 0x0087af,
+ 32: 0x0087d7,
+ 33: 0x0087ff,
+ 34: 0x00af00,
+ 35: 0x00af5f,
+ 36: 0x00af87,
+ 37: 0x00afaf,
+ 38: 0x00afd7,
+ 39: 0x00afff,
+ 40: 0x00d700,
+ 41: 0x00d75f,
+ 42: 0x00d787,
+ 43: 0x00d7af,
+ 44: 0x00d7d7,
+ 45: 0x00d7ff,
+ 46: 0x00ff00,
+ 47: 0x00ff5f,
+ 48: 0x00ff87,
+ 49: 0x00ffaf,
+ 50: 0x00ffd7,
+ 51: 0x00ffff,
+ 52: 0x5f0000,
+ 53: 0x5f005f,
+ 54: 0x5f0087,
+ 55: 0x5f00af,
+ 56: 0x5f00d7,
+ 57: 0x5f00ff,
+ 58: 0x5f5f00,
+ 59: 0x5f5f5f,
+ 60: 0x5f5f87,
+ 61: 0x5f5faf,
+ 62: 0x5f5fd7,
+ 63: 0x5f5fff,
+ 64: 0x5f8700,
+ 65: 0x5f875f,
+ 66: 0x5f8787,
+ 67: 0x5f87af,
+ 68: 0x5f87d7,
+ 69: 0x5f87ff,
+ 70: 0x5faf00,
+ 71: 0x5faf5f,
+ 72: 0x5faf87,
+ 73: 0x5fafaf,
+ 74: 0x5fafd7,
+ 75: 0x5fafff,
+ 76: 0x5fd700,
+ 77: 0x5fd75f,
+ 78: 0x5fd787,
+ 79: 0x5fd7af,
+ 80: 0x5fd7d7,
+ 81: 0x5fd7ff,
+ 82: 0x5fff00,
+ 83: 0x5fff5f,
+ 84: 0x5fff87,
+ 85: 0x5fffaf,
+ 86: 0x5fffd7,
+ 87: 0x5fffff,
+ 88: 0x870000,
+ 89: 0x87005f,
+ 90: 0x870087,
+ 91: 0x8700af,
+ 92: 0x8700d7,
+ 93: 0x8700ff,
+ 94: 0x875f00,
+ 95: 0x875f5f,
+ 96: 0x875f87,
+ 97: 0x875faf,
+ 98: 0x875fd7,
+ 99: 0x875fff,
+ 100: 0x878700,
+ 101: 0x87875f,
+ 102: 0x878787,
+ 103: 0x8787af,
+ 104: 0x8787d7,
+ 105: 0x8787ff,
+ 106: 0x87af00,
+ 107: 0x87af5f,
+ 108: 0x87af87,
+ 109: 0x87afaf,
+ 110: 0x87afd7,
+ 111: 0x87afff,
+ 112: 0x87d700,
+ 113: 0x87d75f,
+ 114: 0x87d787,
+ 115: 0x87d7af,
+ 116: 0x87d7d7,
+ 117: 0x87d7ff,
+ 118: 0x87ff00,
+ 119: 0x87ff5f,
+ 120: 0x87ff87,
+ 121: 0x87ffaf,
+ 122: 0x87ffd7,
+ 123: 0x87ffff,
+ 124: 0xaf0000,
+ 125: 0xaf005f,
+ 126: 0xaf0087,
+ 127: 0xaf00af,
+ 128: 0xaf00d7,
+ 129: 0xaf00ff,
+ 130: 0xaf5f00,
+ 131: 0xaf5f5f,
+ 132: 0xaf5f87,
+ 133: 0xaf5faf,
+ 134: 0xaf5fd7,
+ 135: 0xaf5fff,
+ 136: 0xaf8700,
+ 137: 0xaf875f,
+ 138: 0xaf8787,
+ 139: 0xaf87af,
+ 140: 0xaf87d7,
+ 141: 0xaf87ff,
+ 142: 0xafaf00,
+ 143: 0xafaf5f,
+ 144: 0xafaf87,
+ 145: 0xafafaf,
+ 146: 0xafafd7,
+ 147: 0xafafff,
+ 148: 0xafd700,
+ 149: 0xafd75f,
+ 150: 0xafd787,
+ 151: 0xafd7af,
+ 152: 0xafd7d7,
+ 153: 0xafd7ff,
+ 154: 0xafff00,
+ 155: 0xafff5f,
+ 156: 0xafff87,
+ 157: 0xafffaf,
+ 158: 0xafffd7,
+ 159: 0xafffff,
+ 160: 0xd70000,
+ 161: 0xd7005f,
+ 162: 0xd70087,
+ 163: 0xd700af,
+ 164: 0xd700d7,
+ 165: 0xd700ff,
+ 166: 0xd75f00,
+ 167: 0xd75f5f,
+ 168: 0xd75f87,
+ 169: 0xd75faf,
+ 170: 0xd75fd7,
+ 171: 0xd75fff,
+ 172: 0xd78700,
+ 173: 0xd7875f,
+ 174: 0xd78787,
+ 175: 0xd787af,
+ 176: 0xd787d7,
+ 177: 0xd787ff,
+ 178: 0xd7af00,
+ 179: 0xd7af5f,
+ 180: 0xd7af87,
+ 181: 0xd7afaf,
+ 182: 0xd7afd7,
+ 183: 0xd7afff,
+ 184: 0xd7d700,
+ 185: 0xd7d75f,
+ 186: 0xd7d787,
+ 187: 0xd7d7af,
+ 188: 0xd7d7d7,
+ 189: 0xd7d7ff,
+ 190: 0xd7ff00,
+ 191: 0xd7ff5f,
+ 192: 0xd7ff87,
+ 193: 0xd7ffaf,
+ 194: 0xd7ffd7,
+ 195: 0xd7ffff,
+ 196: 0xff0000,
+ 197: 0xff005f,
+ 198: 0xff0087,
+ 199: 0xff00af,
+ 200: 0xff00d7,
+ 201: 0xff00ff,
+ 202: 0xff5f00,
+ 203: 0xff5f5f,
+ 204: 0xff5f87,
+ 205: 0xff5faf,
+ 206: 0xff5fd7,
+ 207: 0xff5fff,
+ 208: 0xff8700,
+ 209: 0xff875f,
+ 210: 0xff8787,
+ 211: 0xff87af,
+ 212: 0xff87d7,
+ 213: 0xff87ff,
+ 214: 0xffaf00,
+ 215: 0xffaf5f,
+ 216: 0xffaf87,
+ 217: 0xffafaf,
+ 218: 0xffafd7,
+ 219: 0xffafff,
+ 220: 0xffd700,
+ 221: 0xffd75f,
+ 222: 0xffd787,
+ 223: 0xffd7af,
+ 224: 0xffd7d7,
+ 225: 0xffd7ff,
+ 226: 0xffff00,
+ 227: 0xffff5f,
+ 228: 0xffff87,
+ 229: 0xffffaf,
+ 230: 0xffffd7,
+ 231: 0xffffff,
+ 232: 0x080808,
+ 233: 0x121212,
+ 234: 0x1c1c1c,
+ 235: 0x262626,
+ 236: 0x303030,
+ 237: 0x3a3a3a,
+ 238: 0x444444,
+ 239: 0x4e4e4e,
+ 240: 0x585858,
+ 241: 0x626262,
+ 242: 0x6c6c6c,
+ 243: 0x767676,
+ 244: 0x808080,
+ 245: 0x8a8a8a,
+ 246: 0x949494,
+ 247: 0x9e9e9e,
+ 248: 0xa8a8a8,
+ 249: 0xb2b2b2,
+ 250: 0xbcbcbc,
+ 251: 0xc6c6c6,
+ 252: 0xd0d0d0,
+ 253: 0xdadada,
+ 254: 0xe4e4e4,
+ 255: 0xeeeeee,
+}
+
+// `\033]0;TITLESTR\007`
+func doTitleSequence(er *bytes.Reader) error {
+ var c byte
+ var err error
+
+ c, err = er.ReadByte()
+ if err != nil {
+ return err
+ }
+ if c != '0' && c != '2' {
+ return nil
+ }
+ c, err = er.ReadByte()
+ if err != nil {
+ return err
+ }
+ if c != ';' {
+ return nil
+ }
+ title := make([]byte, 0, 80)
+ for {
+ c, err = er.ReadByte()
+ if err != nil {
+ return err
+ }
+ if c == 0x07 || c == '\n' {
+ break
+ }
+ title = append(title, c)
+ }
+ if len(title) > 0 {
+ title8, err := syscall.UTF16PtrFromString(string(title))
+ if err == nil {
+ procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8)))
+ }
+ }
+ return nil
+}
+
+// Write write data on console
+func (w *Writer) Write(data []byte) (n int, err error) {
+ var csbi consoleScreenBufferInfo
+ procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+
+ er := bytes.NewReader(data)
+ var bw [1]byte
+loop:
+ for {
+ c1, err := er.ReadByte()
+ if err != nil {
+ break loop
+ }
+ if c1 != 0x1b {
+ bw[0] = c1
+ w.out.Write(bw[:])
+ continue
+ }
+ c2, err := er.ReadByte()
+ if err != nil {
+ break loop
+ }
+
+ if c2 == ']' {
+ if err := doTitleSequence(er); err != nil {
+ break loop
+ }
+ continue
+ }
+ if c2 != 0x5b {
+ continue
+ }
+
+ var buf bytes.Buffer
+ var m byte
+ for {
+ c, err := er.ReadByte()
+ if err != nil {
+ break loop
+ }
+ if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
+ m = c
+ break
+ }
+ buf.Write([]byte(string(c)))
+ }
+
+ switch m {
+ case 'A':
+ n, err = strconv.Atoi(buf.String())
+ if err != nil {
+ continue
+ }
+ procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+ csbi.cursorPosition.y -= short(n)
+ procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+ case 'B':
+ n, err = strconv.Atoi(buf.String())
+ if err != nil {
+ continue
+ }
+ procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+ csbi.cursorPosition.y += short(n)
+ procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+ case 'C':
+ n, err = strconv.Atoi(buf.String())
+ if err != nil {
+ continue
+ }
+ procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+ csbi.cursorPosition.x += short(n)
+ procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+ case 'D':
+ n, err = strconv.Atoi(buf.String())
+ if err != nil {
+ continue
+ }
+ procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+ csbi.cursorPosition.x -= short(n)
+ procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+ case 'E':
+ n, err = strconv.Atoi(buf.String())
+ if err != nil {
+ continue
+ }
+ procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+ csbi.cursorPosition.x = 0
+ csbi.cursorPosition.y += short(n)
+ procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+ case 'F':
+ n, err = strconv.Atoi(buf.String())
+ if err != nil {
+ continue
+ }
+ procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+ csbi.cursorPosition.x = 0
+ csbi.cursorPosition.y -= short(n)
+ procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+ case 'G':
+ n, err = strconv.Atoi(buf.String())
+ if err != nil {
+ continue
+ }
+ procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+ csbi.cursorPosition.x = short(n - 1)
+ procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+ case 'H', 'f':
+ procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+ if buf.Len() > 0 {
+ token := strings.Split(buf.String(), ";")
+ switch len(token) {
+ case 1:
+ n1, err := strconv.Atoi(token[0])
+ if err != nil {
+ continue
+ }
+ csbi.cursorPosition.y = short(n1 - 1)
+ case 2:
+ n1, err := strconv.Atoi(token[0])
+ if err != nil {
+ continue
+ }
+ n2, err := strconv.Atoi(token[1])
+ if err != nil {
+ continue
+ }
+ csbi.cursorPosition.x = short(n2 - 1)
+ csbi.cursorPosition.y = short(n1 - 1)
+ }
+ } else {
+ csbi.cursorPosition.y = 0
+ }
+ procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+ case 'J':
+ n := 0
+ if buf.Len() > 0 {
+ n, err = strconv.Atoi(buf.String())
+ if err != nil {
+ continue
+ }
+ }
+ var count, written dword
+ var cursor coord
+ procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+ switch n {
+ case 0:
+ cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
+ count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x)
+ case 1:
+ cursor = coord{x: csbi.window.left, y: csbi.window.top}
+ count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.window.top-csbi.cursorPosition.y)*csbi.size.x)
+ case 2:
+ cursor = coord{x: csbi.window.left, y: csbi.window.top}
+ count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x)
+ }
+ procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
+ procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
+ case 'K':
+ n := 0
+ if buf.Len() > 0 {
+ n, err = strconv.Atoi(buf.String())
+ if err != nil {
+ continue
+ }
+ }
+ procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+ var cursor coord
+ var count, written dword
+ switch n {
+ case 0:
+ cursor = coord{x: csbi.cursorPosition.x + 1, y: csbi.cursorPosition.y}
+ count = dword(csbi.size.x - csbi.cursorPosition.x - 1)
+ case 1:
+ cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y}
+ count = dword(csbi.size.x - csbi.cursorPosition.x)
+ case 2:
+ cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y}
+ count = dword(csbi.size.x)
+ }
+ procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
+ procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
+ case 'm':
+ procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+ attr := csbi.attributes
+ cs := buf.String()
+ if cs == "" {
+ procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr))
+ continue
+ }
+ token := strings.Split(cs, ";")
+ for i := 0; i < len(token); i++ {
+ ns := token[i]
+ if n, err = strconv.Atoi(ns); err == nil {
+ switch {
+ case n == 0 || n == 100:
+ attr = w.oldattr
+ case 1 <= n && n <= 5:
+ attr |= foregroundIntensity
+ case n == 7:
+ attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
+ case n == 22 || n == 25:
+ attr |= foregroundIntensity
+ case n == 27:
+ attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
+ case 30 <= n && n <= 37:
+ attr &= backgroundMask
+ if (n-30)&1 != 0 {
+ attr |= foregroundRed
+ }
+ if (n-30)&2 != 0 {
+ attr |= foregroundGreen
+ }
+ if (n-30)&4 != 0 {
+ attr |= foregroundBlue
+ }
+ case n == 38: // set foreground color.
+ if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") {
+ if n256, err := strconv.Atoi(token[i+2]); err == nil {
+ if n256foreAttr == nil {
+ n256setup()
+ }
+ attr &= backgroundMask
+ attr |= n256foreAttr[n256]
+ i += 2
+ }
+ } else {
+ attr = attr & (w.oldattr & backgroundMask)
+ }
+ case n == 39: // reset foreground color.
+ attr &= backgroundMask
+ attr |= w.oldattr & foregroundMask
+ case 40 <= n && n <= 47:
+ attr &= foregroundMask
+ if (n-40)&1 != 0 {
+ attr |= backgroundRed
+ }
+ if (n-40)&2 != 0 {
+ attr |= backgroundGreen
+ }
+ if (n-40)&4 != 0 {
+ attr |= backgroundBlue
+ }
+ case n == 48: // set background color.
+ if i < len(token)-2 && token[i+1] == "5" {
+ if n256, err := strconv.Atoi(token[i+2]); err == nil {
+ if n256backAttr == nil {
+ n256setup()
+ }
+ attr &= foregroundMask
+ attr |= n256backAttr[n256]
+ i += 2
+ }
+ } else {
+ attr = attr & (w.oldattr & foregroundMask)
+ }
+ case n == 49: // reset foreground color.
+ attr &= foregroundMask
+ attr |= w.oldattr & backgroundMask
+ case 90 <= n && n <= 97:
+ attr = (attr & backgroundMask)
+ attr |= foregroundIntensity
+ if (n-90)&1 != 0 {
+ attr |= foregroundRed
+ }
+ if (n-90)&2 != 0 {
+ attr |= foregroundGreen
+ }
+ if (n-90)&4 != 0 {
+ attr |= foregroundBlue
+ }
+ case 100 <= n && n <= 107:
+ attr = (attr & foregroundMask)
+ attr |= backgroundIntensity
+ if (n-100)&1 != 0 {
+ attr |= backgroundRed
+ }
+ if (n-100)&2 != 0 {
+ attr |= backgroundGreen
+ }
+ if (n-100)&4 != 0 {
+ attr |= backgroundBlue
+ }
+ }
+ procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
+ }
+ }
+ case 'h':
+ var ci consoleCursorInfo
+ cs := buf.String()
+ if cs == "5>" {
+ procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
+ ci.visible = 0
+ procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
+ } else if cs == "?25" {
+ procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
+ ci.visible = 1
+ procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
+ }
+ case 'l':
+ var ci consoleCursorInfo
+ cs := buf.String()
+ if cs == "5>" {
+ procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
+ ci.visible = 1
+ procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
+ } else if cs == "?25" {
+ procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
+ ci.visible = 0
+ procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci)))
+ }
+ case 's':
+ procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+ w.oldpos = csbi.cursorPosition
+ case 'u':
+ procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
+ }
+ }
+
+ return len(data), nil
+}
+
+type consoleColor struct {
+ rgb int
+ red bool
+ green bool
+ blue bool
+ intensity bool
+}
+
+func (c consoleColor) foregroundAttr() (attr word) {
+ if c.red {
+ attr |= foregroundRed
+ }
+ if c.green {
+ attr |= foregroundGreen
+ }
+ if c.blue {
+ attr |= foregroundBlue
+ }
+ if c.intensity {
+ attr |= foregroundIntensity
+ }
+ return
+}
+
+func (c consoleColor) backgroundAttr() (attr word) {
+ if c.red {
+ attr |= backgroundRed
+ }
+ if c.green {
+ attr |= backgroundGreen
+ }
+ if c.blue {
+ attr |= backgroundBlue
+ }
+ if c.intensity {
+ attr |= backgroundIntensity
+ }
+ return
+}
+
+var color16 = []consoleColor{
+ {0x000000, false, false, false, false},
+ {0x000080, false, false, true, false},
+ {0x008000, false, true, false, false},
+ {0x008080, false, true, true, false},
+ {0x800000, true, false, false, false},
+ {0x800080, true, false, true, false},
+ {0x808000, true, true, false, false},
+ {0xc0c0c0, true, true, true, false},
+ {0x808080, false, false, false, true},
+ {0x0000ff, false, false, true, true},
+ {0x00ff00, false, true, false, true},
+ {0x00ffff, false, true, true, true},
+ {0xff0000, true, false, false, true},
+ {0xff00ff, true, false, true, true},
+ {0xffff00, true, true, false, true},
+ {0xffffff, true, true, true, true},
+}
+
+type hsv struct {
+ h, s, v float32
+}
+
+func (a hsv) dist(b hsv) float32 {
+ dh := a.h - b.h
+ switch {
+ case dh > 0.5:
+ dh = 1 - dh
+ case dh < -0.5:
+ dh = -1 - dh
+ }
+ ds := a.s - b.s
+ dv := a.v - b.v
+ return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv)))
+}
+
+func toHSV(rgb int) hsv {
+ r, g, b := float32((rgb&0xFF0000)>>16)/256.0,
+ float32((rgb&0x00FF00)>>8)/256.0,
+ float32(rgb&0x0000FF)/256.0
+ min, max := minmax3f(r, g, b)
+ h := max - min
+ if h > 0 {
+ if max == r {
+ h = (g - b) / h
+ if h < 0 {
+ h += 6
+ }
+ } else if max == g {
+ h = 2 + (b-r)/h
+ } else {
+ h = 4 + (r-g)/h
+ }
+ }
+ h /= 6.0
+ s := max - min
+ if max != 0 {
+ s /= max
+ }
+ v := max
+ return hsv{h: h, s: s, v: v}
+}
+
+type hsvTable []hsv
+
+func toHSVTable(rgbTable []consoleColor) hsvTable {
+ t := make(hsvTable, len(rgbTable))
+ for i, c := range rgbTable {
+ t[i] = toHSV(c.rgb)
+ }
+ return t
+}
+
+func (t hsvTable) find(rgb int) consoleColor {
+ hsv := toHSV(rgb)
+ n := 7
+ l := float32(5.0)
+ for i, p := range t {
+ d := hsv.dist(p)
+ if d < l {
+ l, n = d, i
+ }
+ }
+ return color16[n]
+}
+
+func minmax3f(a, b, c float32) (min, max float32) {
+ if a < b {
+ if b < c {
+ return a, c
+ } else if a < c {
+ return a, b
+ } else {
+ return c, b
+ }
+ } else {
+ if a < c {
+ return b, c
+ } else if b < c {
+ return b, a
+ } else {
+ return c, a
+ }
+ }
+}
+
+var n256foreAttr []word
+var n256backAttr []word
+
+func n256setup() {
+ n256foreAttr = make([]word, 256)
+ n256backAttr = make([]word, 256)
+ t := toHSVTable(color16)
+ for i, rgb := range color256 {
+ c := t.find(rgb)
+ n256foreAttr[i] = c.foregroundAttr()
+ n256backAttr[i] = c.backgroundAttr()
+ }
+}
diff --git a/vendor/github.com/mattn/go-colorable/noncolorable.go b/vendor/github.com/mattn/go-colorable/noncolorable.go
new file mode 100644
index 0000000..9721e16
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/noncolorable.go
@@ -0,0 +1,55 @@
+package colorable
+
+import (
+ "bytes"
+ "io"
+)
+
+// NonColorable hold writer but remove escape sequence.
+type NonColorable struct {
+ out io.Writer
+}
+
+// NewNonColorable return new instance of Writer which remove escape sequence from Writer.
+func NewNonColorable(w io.Writer) io.Writer {
+ return &NonColorable{out: w}
+}
+
+// Write write data on console
+func (w *NonColorable) Write(data []byte) (n int, err error) {
+ er := bytes.NewReader(data)
+ var bw [1]byte
+loop:
+ for {
+ c1, err := er.ReadByte()
+ if err != nil {
+ break loop
+ }
+ if c1 != 0x1b {
+ bw[0] = c1
+ w.out.Write(bw[:])
+ continue
+ }
+ c2, err := er.ReadByte()
+ if err != nil {
+ break loop
+ }
+ if c2 != 0x5b {
+ continue
+ }
+
+ var buf bytes.Buffer
+ for {
+ c, err := er.ReadByte()
+ if err != nil {
+ break loop
+ }
+ if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
+ break
+ }
+ buf.Write([]byte(string(c)))
+ }
+ }
+
+ return len(data), nil
+}
diff --git a/vendor/github.com/mattn/go-isatty/.travis.yml b/vendor/github.com/mattn/go-isatty/.travis.yml
new file mode 100644
index 0000000..b9f8b23
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/.travis.yml
@@ -0,0 +1,9 @@
+language: go
+go:
+ - tip
+
+before_install:
+ - go get github.com/mattn/goveralls
+ - go get golang.org/x/tools/cmd/cover
+script:
+ - $HOME/gopath/bin/goveralls -repotoken 3gHdORO5k5ziZcWMBxnd9LrMZaJs8m9x5
diff --git a/vendor/github.com/mattn/go-isatty/LICENSE b/vendor/github.com/mattn/go-isatty/LICENSE
new file mode 100644
index 0000000..65dc692
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/LICENSE
@@ -0,0 +1,9 @@
+Copyright (c) Yasuhiro MATSUMOTO
+
+MIT License (Expat)
+
+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.
diff --git a/vendor/github.com/mattn/go-isatty/README.md b/vendor/github.com/mattn/go-isatty/README.md
new file mode 100644
index 0000000..1e69004
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/README.md
@@ -0,0 +1,50 @@
+# go-isatty
+
+[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty)
+[![Build Status](https://travis-ci.org/mattn/go-isatty.svg?branch=master)](https://travis-ci.org/mattn/go-isatty)
+[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master)
+[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty)
+
+isatty for golang
+
+## Usage
+
+```go
+package main
+
+import (
+ "fmt"
+ "github.com/mattn/go-isatty"
+ "os"
+)
+
+func main() {
+ if isatty.IsTerminal(os.Stdout.Fd()) {
+ fmt.Println("Is Terminal")
+ } else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
+ fmt.Println("Is Cygwin/MSYS2 Terminal")
+ } else {
+ fmt.Println("Is Not Terminal")
+ }
+}
+```
+
+## Installation
+
+```
+$ go get github.com/mattn/go-isatty
+```
+
+## License
+
+MIT
+
+## Author
+
+Yasuhiro Matsumoto (a.k.a mattn)
+
+## Thanks
+
+* k-takata: base idea for IsCygwinTerminal
+
+ https://github.com/k-takata/go-iscygpty
diff --git a/vendor/github.com/mattn/go-isatty/doc.go b/vendor/github.com/mattn/go-isatty/doc.go
new file mode 100644
index 0000000..17d4f90
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/doc.go
@@ -0,0 +1,2 @@
+// Package isatty implements interface to isatty
+package isatty
diff --git a/vendor/github.com/mattn/go-isatty/example_test.go b/vendor/github.com/mattn/go-isatty/example_test.go
new file mode 100644
index 0000000..fa8f7e7
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/example_test.go
@@ -0,0 +1,18 @@
+package isatty_test
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/mattn/go-isatty"
+)
+
+func Example() {
+ if isatty.IsTerminal(os.Stdout.Fd()) {
+ fmt.Println("Is Terminal")
+ } else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
+ fmt.Println("Is Cygwin/MSYS2 Terminal")
+ } else {
+ fmt.Println("Is Not Terminal")
+ }
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_appengine.go b/vendor/github.com/mattn/go-isatty/isatty_appengine.go
new file mode 100644
index 0000000..9584a98
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_appengine.go
@@ -0,0 +1,15 @@
+// +build appengine
+
+package isatty
+
+// IsTerminal returns true if the file descriptor is terminal which
+// is always false on on appengine classic which is a sandboxed PaaS.
+func IsTerminal(fd uintptr) bool {
+ return false
+}
+
+// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
+// terminal. This is also always false on this environment.
+func IsCygwinTerminal(fd uintptr) bool {
+ return false
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/mattn/go-isatty/isatty_bsd.go
new file mode 100644
index 0000000..42f2514
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_bsd.go
@@ -0,0 +1,18 @@
+// +build darwin freebsd openbsd netbsd dragonfly
+// +build !appengine
+
+package isatty
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+const ioctlReadTermios = syscall.TIOCGETA
+
+// IsTerminal return true if the file descriptor is terminal.
+func IsTerminal(fd uintptr) bool {
+ var termios syscall.Termios
+ _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
+ return err == 0
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_linux.go b/vendor/github.com/mattn/go-isatty/isatty_linux.go
new file mode 100644
index 0000000..7384cf9
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_linux.go
@@ -0,0 +1,18 @@
+// +build linux
+// +build !appengine,!ppc64,!ppc64le
+
+package isatty
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+const ioctlReadTermios = syscall.TCGETS
+
+// IsTerminal return true if the file descriptor is terminal.
+func IsTerminal(fd uintptr) bool {
+ var termios syscall.Termios
+ _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
+ return err == 0
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go b/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go
new file mode 100644
index 0000000..44e5d21
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go
@@ -0,0 +1,19 @@
+// +build linux
+// +build ppc64 ppc64le
+
+package isatty
+
+import (
+ "unsafe"
+
+ syscall "golang.org/x/sys/unix"
+)
+
+const ioctlReadTermios = syscall.TCGETS
+
+// IsTerminal return true if the file descriptor is terminal.
+func IsTerminal(fd uintptr) bool {
+ var termios syscall.Termios
+ _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
+ return err == 0
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_others.go b/vendor/github.com/mattn/go-isatty/isatty_others.go
new file mode 100644
index 0000000..ff4de3d
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_others.go
@@ -0,0 +1,10 @@
+// +build !windows
+// +build !appengine
+
+package isatty
+
+// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
+// terminal. This is also always false on this environment.
+func IsCygwinTerminal(fd uintptr) bool {
+ return false
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_others_test.go b/vendor/github.com/mattn/go-isatty/isatty_others_test.go
new file mode 100644
index 0000000..a2091cf
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_others_test.go
@@ -0,0 +1,19 @@
+// +build !windows
+
+package isatty
+
+import (
+ "os"
+ "testing"
+)
+
+func TestTerminal(t *testing.T) {
+ // test for non-panic
+ IsTerminal(os.Stdout.Fd())
+}
+
+func TestCygwinPipeName(t *testing.T) {
+ if IsCygwinTerminal(os.Stdout.Fd()) {
+ t.Fatal("should be false always")
+ }
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_solaris.go b/vendor/github.com/mattn/go-isatty/isatty_solaris.go
new file mode 100644
index 0000000..1f0c6bf
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_solaris.go
@@ -0,0 +1,16 @@
+// +build solaris
+// +build !appengine
+
+package isatty
+
+import (
+ "golang.org/x/sys/unix"
+)
+
+// IsTerminal returns true if the given file descriptor is a terminal.
+// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c
+func IsTerminal(fd uintptr) bool {
+ var termio unix.Termio
+ err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio)
+ return err == nil
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_windows.go b/vendor/github.com/mattn/go-isatty/isatty_windows.go
new file mode 100644
index 0000000..af51cbc
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_windows.go
@@ -0,0 +1,94 @@
+// +build windows
+// +build !appengine
+
+package isatty
+
+import (
+ "strings"
+ "syscall"
+ "unicode/utf16"
+ "unsafe"
+)
+
+const (
+ fileNameInfo uintptr = 2
+ fileTypePipe = 3
+)
+
+var (
+ kernel32 = syscall.NewLazyDLL("kernel32.dll")
+ procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
+ procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
+ procGetFileType = kernel32.NewProc("GetFileType")
+)
+
+func init() {
+ // Check if GetFileInformationByHandleEx is available.
+ if procGetFileInformationByHandleEx.Find() != nil {
+ procGetFileInformationByHandleEx = nil
+ }
+}
+
+// IsTerminal return true if the file descriptor is terminal.
+func IsTerminal(fd uintptr) bool {
+ var st uint32
+ r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
+ return r != 0 && e == 0
+}
+
+// Check pipe name is used for cygwin/msys2 pty.
+// Cygwin/MSYS2 PTY has a name like:
+// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master
+func isCygwinPipeName(name string) bool {
+ token := strings.Split(name, "-")
+ if len(token) < 5 {
+ return false
+ }
+
+ if token[0] != `\msys` && token[0] != `\cygwin` {
+ return false
+ }
+
+ if token[1] == "" {
+ return false
+ }
+
+ if !strings.HasPrefix(token[2], "pty") {
+ return false
+ }
+
+ if token[3] != `from` && token[3] != `to` {
+ return false
+ }
+
+ if token[4] != "master" {
+ return false
+ }
+
+ return true
+}
+
+// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
+// terminal.
+func IsCygwinTerminal(fd uintptr) bool {
+ if procGetFileInformationByHandleEx == nil {
+ return false
+ }
+
+ // Cygwin/msys's pty is a pipe.
+ ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0)
+ if ft != fileTypePipe || e != 0 {
+ return false
+ }
+
+ var buf [2 + syscall.MAX_PATH]uint16
+ r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(),
+ 4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)),
+ uintptr(len(buf)*2), 0, 0)
+ if r == 0 || e != 0 {
+ return false
+ }
+
+ l := *(*uint32)(unsafe.Pointer(&buf))
+ return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2])))
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_windows_test.go b/vendor/github.com/mattn/go-isatty/isatty_windows_test.go
new file mode 100644
index 0000000..777e8a6
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_windows_test.go
@@ -0,0 +1,35 @@
+// +build windows
+
+package isatty
+
+import (
+ "testing"
+)
+
+func TestCygwinPipeName(t *testing.T) {
+ tests := []struct {
+ name string
+ result bool
+ }{
+ {``, false},
+ {`\msys-`, false},
+ {`\cygwin-----`, false},
+ {`\msys-x-PTY5-pty1-from-master`, false},
+ {`\cygwin-x-PTY5-from-master`, false},
+ {`\cygwin-x-pty2-from-toaster`, false},
+ {`\cygwin--pty2-from-master`, false},
+ {`\\cygwin-x-pty2-from-master`, false},
+ {`\cygwin-x-pty2-from-master-`, true}, // for the feature
+ {`\cygwin-e022582115c10879-pty4-from-master`, true},
+ {`\msys-e022582115c10879-pty4-to-master`, true},
+ {`\cygwin-e022582115c10879-pty4-to-master`, true},
+ }
+
+ for _, test := range tests {
+ want := test.result
+ got := isCygwinPipeName(test.name)
+ if want != got {
+ t.Fatalf("isatty(%q): got %v, want %v:", test.name, got, want)
+ }
+ }
+}
diff --git a/vendor/github.com/mattn/go-runewidth/.travis.yml b/vendor/github.com/mattn/go-runewidth/.travis.yml
new file mode 100644
index 0000000..5c9c2a3
--- /dev/null
+++ b/vendor/github.com/mattn/go-runewidth/.travis.yml
@@ -0,0 +1,8 @@
+language: go
+go:
+ - tip
+before_install:
+ - go get github.com/mattn/goveralls
+ - go get golang.org/x/tools/cmd/cover
+script:
+ - $HOME/gopath/bin/goveralls -repotoken lAKAWPzcGsD3A8yBX3BGGtRUdJ6CaGERL
diff --git a/vendor/github.com/mattn/go-runewidth/LICENSE b/vendor/github.com/mattn/go-runewidth/LICENSE
new file mode 100644
index 0000000..91b5cef
--- /dev/null
+++ b/vendor/github.com/mattn/go-runewidth/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Yasuhiro Matsumoto
+
+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.
diff --git a/vendor/github.com/mattn/go-runewidth/README.mkd b/vendor/github.com/mattn/go-runewidth/README.mkd
new file mode 100644
index 0000000..66663a9
--- /dev/null
+++ b/vendor/github.com/mattn/go-runewidth/README.mkd
@@ -0,0 +1,27 @@
+go-runewidth
+============
+
+[![Build Status](https://travis-ci.org/mattn/go-runewidth.png?branch=master)](https://travis-ci.org/mattn/go-runewidth)
+[![Coverage Status](https://coveralls.io/repos/mattn/go-runewidth/badge.png?branch=HEAD)](https://coveralls.io/r/mattn/go-runewidth?branch=HEAD)
+[![GoDoc](https://godoc.org/github.com/mattn/go-runewidth?status.svg)](http://godoc.org/github.com/mattn/go-runewidth)
+[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-runewidth)](https://goreportcard.com/report/github.com/mattn/go-runewidth)
+
+Provides functions to get fixed width of the character or string.
+
+Usage
+-----
+
+```go
+runewidth.StringWidth("つのだ☆HIRO") == 12
+```
+
+
+Author
+------
+
+Yasuhiro Matsumoto
+
+License
+-------
+
+under the MIT License: http://mattn.mit-license.org/2013
diff --git a/vendor/github.com/mattn/go-runewidth/runewidth.go b/vendor/github.com/mattn/go-runewidth/runewidth.go
new file mode 100644
index 0000000..2164497
--- /dev/null
+++ b/vendor/github.com/mattn/go-runewidth/runewidth.go
@@ -0,0 +1,1223 @@
+package runewidth
+
+var (
+ // EastAsianWidth will be set true if the current locale is CJK
+ EastAsianWidth = IsEastAsian()
+
+ // DefaultCondition is a condition in current locale
+ DefaultCondition = &Condition{EastAsianWidth}
+)
+
+type interval struct {
+ first rune
+ last rune
+}
+
+type table []interval
+
+func inTables(r rune, ts ...table) bool {
+ for _, t := range ts {
+ if inTable(r, t) {
+ return true
+ }
+ }
+ return false
+}
+
+func inTable(r rune, t table) bool {
+ // func (t table) IncludesRune(r rune) bool {
+ if r < t[0].first {
+ return false
+ }
+
+ bot := 0
+ top := len(t) - 1
+ for top >= bot {
+ mid := (bot + top) / 2
+
+ switch {
+ case t[mid].last < r:
+ bot = mid + 1
+ case t[mid].first > r:
+ top = mid - 1
+ default:
+ return true
+ }
+ }
+
+ return false
+}
+
+var private = table{
+ {0x00E000, 0x00F8FF}, {0x0F0000, 0x0FFFFD}, {0x100000, 0x10FFFD},
+}
+
+var nonprint = table{
+ {0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
+ {0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
+ {0x202A, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
+ {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
+}
+
+var combining = table{
+ {0x0300, 0x036F}, {0x0483, 0x0489}, {0x0591, 0x05BD},
+ {0x05BF, 0x05BF}, {0x05C1, 0x05C2}, {0x05C4, 0x05C5},
+ {0x05C7, 0x05C7}, {0x0610, 0x061A}, {0x064B, 0x065F},
+ {0x0670, 0x0670}, {0x06D6, 0x06DC}, {0x06DF, 0x06E4},
+ {0x06E7, 0x06E8}, {0x06EA, 0x06ED}, {0x0711, 0x0711},
+ {0x0730, 0x074A}, {0x07A6, 0x07B0}, {0x07EB, 0x07F3},
+ {0x0816, 0x0819}, {0x081B, 0x0823}, {0x0825, 0x0827},
+ {0x0829, 0x082D}, {0x0859, 0x085B}, {0x08D4, 0x08E1},
+ {0x08E3, 0x0903}, {0x093A, 0x093C}, {0x093E, 0x094F},
+ {0x0951, 0x0957}, {0x0962, 0x0963}, {0x0981, 0x0983},
+ {0x09BC, 0x09BC}, {0x09BE, 0x09C4}, {0x09C7, 0x09C8},
+ {0x09CB, 0x09CD}, {0x09D7, 0x09D7}, {0x09E2, 0x09E3},
+ {0x0A01, 0x0A03}, {0x0A3C, 0x0A3C}, {0x0A3E, 0x0A42},
+ {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A51, 0x0A51},
+ {0x0A70, 0x0A71}, {0x0A75, 0x0A75}, {0x0A81, 0x0A83},
+ {0x0ABC, 0x0ABC}, {0x0ABE, 0x0AC5}, {0x0AC7, 0x0AC9},
+ {0x0ACB, 0x0ACD}, {0x0AE2, 0x0AE3}, {0x0B01, 0x0B03},
+ {0x0B3C, 0x0B3C}, {0x0B3E, 0x0B44}, {0x0B47, 0x0B48},
+ {0x0B4B, 0x0B4D}, {0x0B56, 0x0B57}, {0x0B62, 0x0B63},
+ {0x0B82, 0x0B82}, {0x0BBE, 0x0BC2}, {0x0BC6, 0x0BC8},
+ {0x0BCA, 0x0BCD}, {0x0BD7, 0x0BD7}, {0x0C00, 0x0C03},
+ {0x0C3E, 0x0C44}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D},
+ {0x0C55, 0x0C56}, {0x0C62, 0x0C63}, {0x0C81, 0x0C83},
+ {0x0CBC, 0x0CBC}, {0x0CBE, 0x0CC4}, {0x0CC6, 0x0CC8},
+ {0x0CCA, 0x0CCD}, {0x0CD5, 0x0CD6}, {0x0CE2, 0x0CE3},
+ {0x0D01, 0x0D03}, {0x0D3E, 0x0D44}, {0x0D46, 0x0D48},
+ {0x0D4A, 0x0D4D}, {0x0D57, 0x0D57}, {0x0D62, 0x0D63},
+ {0x0D82, 0x0D83}, {0x0DCA, 0x0DCA}, {0x0DCF, 0x0DD4},
+ {0x0DD6, 0x0DD6}, {0x0DD8, 0x0DDF}, {0x0DF2, 0x0DF3},
+ {0x0E31, 0x0E31}, {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E},
+ {0x0EB1, 0x0EB1}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC},
+ {0x0EC8, 0x0ECD}, {0x0F18, 0x0F19}, {0x0F35, 0x0F35},
+ {0x0F37, 0x0F37}, {0x0F39, 0x0F39}, {0x0F3E, 0x0F3F},
+ {0x0F71, 0x0F84}, {0x0F86, 0x0F87}, {0x0F8D, 0x0F97},
+ {0x0F99, 0x0FBC}, {0x0FC6, 0x0FC6}, {0x102B, 0x103E},
+ {0x1056, 0x1059}, {0x105E, 0x1060}, {0x1062, 0x1064},
+ {0x1067, 0x106D}, {0x1071, 0x1074}, {0x1082, 0x108D},
+ {0x108F, 0x108F}, {0x109A, 0x109D}, {0x135D, 0x135F},
+ {0x1712, 0x1714}, {0x1732, 0x1734}, {0x1752, 0x1753},
+ {0x1772, 0x1773}, {0x17B4, 0x17D3}, {0x17DD, 0x17DD},
+ {0x180B, 0x180D}, {0x1885, 0x1886}, {0x18A9, 0x18A9},
+ {0x1920, 0x192B}, {0x1930, 0x193B}, {0x1A17, 0x1A1B},
+ {0x1A55, 0x1A5E}, {0x1A60, 0x1A7C}, {0x1A7F, 0x1A7F},
+ {0x1AB0, 0x1ABE}, {0x1B00, 0x1B04}, {0x1B34, 0x1B44},
+ {0x1B6B, 0x1B73}, {0x1B80, 0x1B82}, {0x1BA1, 0x1BAD},
+ {0x1BE6, 0x1BF3}, {0x1C24, 0x1C37}, {0x1CD0, 0x1CD2},
+ {0x1CD4, 0x1CE8}, {0x1CED, 0x1CED}, {0x1CF2, 0x1CF4},
+ {0x1CF8, 0x1CF9}, {0x1DC0, 0x1DF5}, {0x1DFB, 0x1DFF},
+ {0x20D0, 0x20F0}, {0x2CEF, 0x2CF1}, {0x2D7F, 0x2D7F},
+ {0x2DE0, 0x2DFF}, {0x302A, 0x302F}, {0x3099, 0x309A},
+ {0xA66F, 0xA672}, {0xA674, 0xA67D}, {0xA69E, 0xA69F},
+ {0xA6F0, 0xA6F1}, {0xA802, 0xA802}, {0xA806, 0xA806},
+ {0xA80B, 0xA80B}, {0xA823, 0xA827}, {0xA880, 0xA881},
+ {0xA8B4, 0xA8C5}, {0xA8E0, 0xA8F1}, {0xA926, 0xA92D},
+ {0xA947, 0xA953}, {0xA980, 0xA983}, {0xA9B3, 0xA9C0},
+ {0xA9E5, 0xA9E5}, {0xAA29, 0xAA36}, {0xAA43, 0xAA43},
+ {0xAA4C, 0xAA4D}, {0xAA7B, 0xAA7D}, {0xAAB0, 0xAAB0},
+ {0xAAB2, 0xAAB4}, {0xAAB7, 0xAAB8}, {0xAABE, 0xAABF},
+ {0xAAC1, 0xAAC1}, {0xAAEB, 0xAAEF}, {0xAAF5, 0xAAF6},
+ {0xABE3, 0xABEA}, {0xABEC, 0xABED}, {0xFB1E, 0xFB1E},
+ {0xFE00, 0xFE0F}, {0xFE20, 0xFE2F}, {0x101FD, 0x101FD},
+ {0x102E0, 0x102E0}, {0x10376, 0x1037A}, {0x10A01, 0x10A03},
+ {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F}, {0x10A38, 0x10A3A},
+ {0x10A3F, 0x10A3F}, {0x10AE5, 0x10AE6}, {0x11000, 0x11002},
+ {0x11038, 0x11046}, {0x1107F, 0x11082}, {0x110B0, 0x110BA},
+ {0x11100, 0x11102}, {0x11127, 0x11134}, {0x11173, 0x11173},
+ {0x11180, 0x11182}, {0x111B3, 0x111C0}, {0x111CA, 0x111CC},
+ {0x1122C, 0x11237}, {0x1123E, 0x1123E}, {0x112DF, 0x112EA},
+ {0x11300, 0x11303}, {0x1133C, 0x1133C}, {0x1133E, 0x11344},
+ {0x11347, 0x11348}, {0x1134B, 0x1134D}, {0x11357, 0x11357},
+ {0x11362, 0x11363}, {0x11366, 0x1136C}, {0x11370, 0x11374},
+ {0x11435, 0x11446}, {0x114B0, 0x114C3}, {0x115AF, 0x115B5},
+ {0x115B8, 0x115C0}, {0x115DC, 0x115DD}, {0x11630, 0x11640},
+ {0x116AB, 0x116B7}, {0x1171D, 0x1172B}, {0x11C2F, 0x11C36},
+ {0x11C38, 0x11C3F}, {0x11C92, 0x11CA7}, {0x11CA9, 0x11CB6},
+ {0x16AF0, 0x16AF4}, {0x16B30, 0x16B36}, {0x16F51, 0x16F7E},
+ {0x16F8F, 0x16F92}, {0x1BC9D, 0x1BC9E}, {0x1D165, 0x1D169},
+ {0x1D16D, 0x1D172}, {0x1D17B, 0x1D182}, {0x1D185, 0x1D18B},
+ {0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244}, {0x1DA00, 0x1DA36},
+ {0x1DA3B, 0x1DA6C}, {0x1DA75, 0x1DA75}, {0x1DA84, 0x1DA84},
+ {0x1DA9B, 0x1DA9F}, {0x1DAA1, 0x1DAAF}, {0x1E000, 0x1E006},
+ {0x1E008, 0x1E018}, {0x1E01B, 0x1E021}, {0x1E023, 0x1E024},
+ {0x1E026, 0x1E02A}, {0x1E8D0, 0x1E8D6}, {0x1E944, 0x1E94A},
+ {0xE0100, 0xE01EF},
+}
+
+var doublewidth = table{
+ {0x1100, 0x115F}, {0x231A, 0x231B}, {0x2329, 0x232A},
+ {0x23E9, 0x23EC}, {0x23F0, 0x23F0}, {0x23F3, 0x23F3},
+ {0x25FD, 0x25FE}, {0x2614, 0x2615}, {0x2648, 0x2653},
+ {0x267F, 0x267F}, {0x2693, 0x2693}, {0x26A1, 0x26A1},
+ {0x26AA, 0x26AB}, {0x26BD, 0x26BE}, {0x26C4, 0x26C5},
+ {0x26CE, 0x26CE}, {0x26D4, 0x26D4}, {0x26EA, 0x26EA},
+ {0x26F2, 0x26F3}, {0x26F5, 0x26F5}, {0x26FA, 0x26FA},
+ {0x26FD, 0x26FD}, {0x2705, 0x2705}, {0x270A, 0x270B},
+ {0x2728, 0x2728}, {0x274C, 0x274C}, {0x274E, 0x274E},
+ {0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
+ {0x27B0, 0x27B0}, {0x27BF, 0x27BF}, {0x2B1B, 0x2B1C},
+ {0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x2E80, 0x2E99},
+ {0x2E9B, 0x2EF3}, {0x2F00, 0x2FD5}, {0x2FF0, 0x2FFB},
+ {0x3000, 0x303E}, {0x3041, 0x3096}, {0x3099, 0x30FF},
+ {0x3105, 0x312D}, {0x3131, 0x318E}, {0x3190, 0x31BA},
+ {0x31C0, 0x31E3}, {0x31F0, 0x321E}, {0x3220, 0x3247},
+ {0x3250, 0x32FE}, {0x3300, 0x4DBF}, {0x4E00, 0xA48C},
+ {0xA490, 0xA4C6}, {0xA960, 0xA97C}, {0xAC00, 0xD7A3},
+ {0xF900, 0xFAFF}, {0xFE10, 0xFE19}, {0xFE30, 0xFE52},
+ {0xFE54, 0xFE66}, {0xFE68, 0xFE6B}, {0xFF01, 0xFF60},
+ {0xFFE0, 0xFFE6}, {0x16FE0, 0x16FE0}, {0x17000, 0x187EC},
+ {0x18800, 0x18AF2}, {0x1B000, 0x1B001}, {0x1F004, 0x1F004},
+ {0x1F0CF, 0x1F0CF}, {0x1F18E, 0x1F18E}, {0x1F191, 0x1F19A},
+ {0x1F200, 0x1F202}, {0x1F210, 0x1F23B}, {0x1F240, 0x1F248},
+ {0x1F250, 0x1F251}, {0x1F300, 0x1F320}, {0x1F32D, 0x1F335},
+ {0x1F337, 0x1F37C}, {0x1F37E, 0x1F393}, {0x1F3A0, 0x1F3CA},
+ {0x1F3CF, 0x1F3D3}, {0x1F3E0, 0x1F3F0}, {0x1F3F4, 0x1F3F4},
+ {0x1F3F8, 0x1F43E}, {0x1F440, 0x1F440}, {0x1F442, 0x1F4FC},
+ {0x1F4FF, 0x1F53D}, {0x1F54B, 0x1F54E}, {0x1F550, 0x1F567},
+ {0x1F57A, 0x1F57A}, {0x1F595, 0x1F596}, {0x1F5A4, 0x1F5A4},
+ {0x1F5FB, 0x1F64F}, {0x1F680, 0x1F6C5}, {0x1F6CC, 0x1F6CC},
+ {0x1F6D0, 0x1F6D2}, {0x1F6EB, 0x1F6EC}, {0x1F6F4, 0x1F6F6},
+ {0x1F910, 0x1F91E}, {0x1F920, 0x1F927}, {0x1F930, 0x1F930},
+ {0x1F933, 0x1F93E}, {0x1F940, 0x1F94B}, {0x1F950, 0x1F95E},
+ {0x1F980, 0x1F991}, {0x1F9C0, 0x1F9C0}, {0x20000, 0x2FFFD},
+ {0x30000, 0x3FFFD},
+}
+
+var ambiguous = table{
+ {0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8},
+ {0x00AA, 0x00AA}, {0x00AD, 0x00AE}, {0x00B0, 0x00B4},
+ {0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6},
+ {0x00D0, 0x00D0}, {0x00D7, 0x00D8}, {0x00DE, 0x00E1},
+ {0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED},
+ {0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA},
+ {0x00FC, 0x00FC}, {0x00FE, 0x00FE}, {0x0101, 0x0101},
+ {0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B},
+ {0x0126, 0x0127}, {0x012B, 0x012B}, {0x0131, 0x0133},
+ {0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144},
+ {0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153},
+ {0x0166, 0x0167}, {0x016B, 0x016B}, {0x01CE, 0x01CE},
+ {0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4},
+ {0x01D6, 0x01D6}, {0x01D8, 0x01D8}, {0x01DA, 0x01DA},
+ {0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261},
+ {0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB},
+ {0x02CD, 0x02CD}, {0x02D0, 0x02D0}, {0x02D8, 0x02DB},
+ {0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0300, 0x036F},
+ {0x0391, 0x03A1}, {0x03A3, 0x03A9}, {0x03B1, 0x03C1},
+ {0x03C3, 0x03C9}, {0x0401, 0x0401}, {0x0410, 0x044F},
+ {0x0451, 0x0451}, {0x2010, 0x2010}, {0x2013, 0x2016},
+ {0x2018, 0x2019}, {0x201C, 0x201D}, {0x2020, 0x2022},
+ {0x2024, 0x2027}, {0x2030, 0x2030}, {0x2032, 0x2033},
+ {0x2035, 0x2035}, {0x203B, 0x203B}, {0x203E, 0x203E},
+ {0x2074, 0x2074}, {0x207F, 0x207F}, {0x2081, 0x2084},
+ {0x20AC, 0x20AC}, {0x2103, 0x2103}, {0x2105, 0x2105},
+ {0x2109, 0x2109}, {0x2113, 0x2113}, {0x2116, 0x2116},
+ {0x2121, 0x2122}, {0x2126, 0x2126}, {0x212B, 0x212B},
+ {0x2153, 0x2154}, {0x215B, 0x215E}, {0x2160, 0x216B},
+ {0x2170, 0x2179}, {0x2189, 0x2189}, {0x2190, 0x2199},
+ {0x21B8, 0x21B9}, {0x21D2, 0x21D2}, {0x21D4, 0x21D4},
+ {0x21E7, 0x21E7}, {0x2200, 0x2200}, {0x2202, 0x2203},
+ {0x2207, 0x2208}, {0x220B, 0x220B}, {0x220F, 0x220F},
+ {0x2211, 0x2211}, {0x2215, 0x2215}, {0x221A, 0x221A},
+ {0x221D, 0x2220}, {0x2223, 0x2223}, {0x2225, 0x2225},
+ {0x2227, 0x222C}, {0x222E, 0x222E}, {0x2234, 0x2237},
+ {0x223C, 0x223D}, {0x2248, 0x2248}, {0x224C, 0x224C},
+ {0x2252, 0x2252}, {0x2260, 0x2261}, {0x2264, 0x2267},
+ {0x226A, 0x226B}, {0x226E, 0x226F}, {0x2282, 0x2283},
+ {0x2286, 0x2287}, {0x2295, 0x2295}, {0x2299, 0x2299},
+ {0x22A5, 0x22A5}, {0x22BF, 0x22BF}, {0x2312, 0x2312},
+ {0x2460, 0x24E9}, {0x24EB, 0x254B}, {0x2550, 0x2573},
+ {0x2580, 0x258F}, {0x2592, 0x2595}, {0x25A0, 0x25A1},
+ {0x25A3, 0x25A9}, {0x25B2, 0x25B3}, {0x25B6, 0x25B7},
+ {0x25BC, 0x25BD}, {0x25C0, 0x25C1}, {0x25C6, 0x25C8},
+ {0x25CB, 0x25CB}, {0x25CE, 0x25D1}, {0x25E2, 0x25E5},
+ {0x25EF, 0x25EF}, {0x2605, 0x2606}, {0x2609, 0x2609},
+ {0x260E, 0x260F}, {0x261C, 0x261C}, {0x261E, 0x261E},
+ {0x2640, 0x2640}, {0x2642, 0x2642}, {0x2660, 0x2661},
+ {0x2663, 0x2665}, {0x2667, 0x266A}, {0x266C, 0x266D},
+ {0x266F, 0x266F}, {0x269E, 0x269F}, {0x26BF, 0x26BF},
+ {0x26C6, 0x26CD}, {0x26CF, 0x26D3}, {0x26D5, 0x26E1},
+ {0x26E3, 0x26E3}, {0x26E8, 0x26E9}, {0x26EB, 0x26F1},
+ {0x26F4, 0x26F4}, {0x26F6, 0x26F9}, {0x26FB, 0x26FC},
+ {0x26FE, 0x26FF}, {0x273D, 0x273D}, {0x2776, 0x277F},
+ {0x2B56, 0x2B59}, {0x3248, 0x324F}, {0xE000, 0xF8FF},
+ {0xFE00, 0xFE0F}, {0xFFFD, 0xFFFD}, {0x1F100, 0x1F10A},
+ {0x1F110, 0x1F12D}, {0x1F130, 0x1F169}, {0x1F170, 0x1F18D},
+ {0x1F18F, 0x1F190}, {0x1F19B, 0x1F1AC}, {0xE0100, 0xE01EF},
+ {0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD},
+}
+
+var emoji = table{
+ {0x1F1E6, 0x1F1FF}, {0x1F321, 0x1F321}, {0x1F324, 0x1F32C},
+ {0x1F336, 0x1F336}, {0x1F37D, 0x1F37D}, {0x1F396, 0x1F397},
+ {0x1F399, 0x1F39B}, {0x1F39E, 0x1F39F}, {0x1F3CB, 0x1F3CE},
+ {0x1F3D4, 0x1F3DF}, {0x1F3F3, 0x1F3F5}, {0x1F3F7, 0x1F3F7},
+ {0x1F43F, 0x1F43F}, {0x1F441, 0x1F441}, {0x1F4FD, 0x1F4FD},
+ {0x1F549, 0x1F54A}, {0x1F56F, 0x1F570}, {0x1F573, 0x1F579},
+ {0x1F587, 0x1F587}, {0x1F58A, 0x1F58D}, {0x1F590, 0x1F590},
+ {0x1F5A5, 0x1F5A5}, {0x1F5A8, 0x1F5A8}, {0x1F5B1, 0x1F5B2},
+ {0x1F5BC, 0x1F5BC}, {0x1F5C2, 0x1F5C4}, {0x1F5D1, 0x1F5D3},
+ {0x1F5DC, 0x1F5DE}, {0x1F5E1, 0x1F5E1}, {0x1F5E3, 0x1F5E3},
+ {0x1F5E8, 0x1F5E8}, {0x1F5EF, 0x1F5EF}, {0x1F5F3, 0x1F5F3},
+ {0x1F5FA, 0x1F5FA}, {0x1F6CB, 0x1F6CF}, {0x1F6E0, 0x1F6E5},
+ {0x1F6E9, 0x1F6E9}, {0x1F6F0, 0x1F6F0}, {0x1F6F3, 0x1F6F3},
+}
+
+var notassigned = table{
+ {0x0378, 0x0379}, {0x0380, 0x0383}, {0x038B, 0x038B},
+ {0x038D, 0x038D}, {0x03A2, 0x03A2}, {0x0530, 0x0530},
+ {0x0557, 0x0558}, {0x0560, 0x0560}, {0x0588, 0x0588},
+ {0x058B, 0x058C}, {0x0590, 0x0590}, {0x05C8, 0x05CF},
+ {0x05EB, 0x05EF}, {0x05F5, 0x05FF}, {0x061D, 0x061D},
+ {0x070E, 0x070E}, {0x074B, 0x074C}, {0x07B2, 0x07BF},
+ {0x07FB, 0x07FF}, {0x082E, 0x082F}, {0x083F, 0x083F},
+ {0x085C, 0x085D}, {0x085F, 0x089F}, {0x08B5, 0x08B5},
+ {0x08BE, 0x08D3}, {0x0984, 0x0984}, {0x098D, 0x098E},
+ {0x0991, 0x0992}, {0x09A9, 0x09A9}, {0x09B1, 0x09B1},
+ {0x09B3, 0x09B5}, {0x09BA, 0x09BB}, {0x09C5, 0x09C6},
+ {0x09C9, 0x09CA}, {0x09CF, 0x09D6}, {0x09D8, 0x09DB},
+ {0x09DE, 0x09DE}, {0x09E4, 0x09E5}, {0x09FC, 0x0A00},
+ {0x0A04, 0x0A04}, {0x0A0B, 0x0A0E}, {0x0A11, 0x0A12},
+ {0x0A29, 0x0A29}, {0x0A31, 0x0A31}, {0x0A34, 0x0A34},
+ {0x0A37, 0x0A37}, {0x0A3A, 0x0A3B}, {0x0A3D, 0x0A3D},
+ {0x0A43, 0x0A46}, {0x0A49, 0x0A4A}, {0x0A4E, 0x0A50},
+ {0x0A52, 0x0A58}, {0x0A5D, 0x0A5D}, {0x0A5F, 0x0A65},
+ {0x0A76, 0x0A80}, {0x0A84, 0x0A84}, {0x0A8E, 0x0A8E},
+ {0x0A92, 0x0A92}, {0x0AA9, 0x0AA9}, {0x0AB1, 0x0AB1},
+ {0x0AB4, 0x0AB4}, {0x0ABA, 0x0ABB}, {0x0AC6, 0x0AC6},
+ {0x0ACA, 0x0ACA}, {0x0ACE, 0x0ACF}, {0x0AD1, 0x0ADF},
+ {0x0AE4, 0x0AE5}, {0x0AF2, 0x0AF8}, {0x0AFA, 0x0B00},
+ {0x0B04, 0x0B04}, {0x0B0D, 0x0B0E}, {0x0B11, 0x0B12},
+ {0x0B29, 0x0B29}, {0x0B31, 0x0B31}, {0x0B34, 0x0B34},
+ {0x0B3A, 0x0B3B}, {0x0B45, 0x0B46}, {0x0B49, 0x0B4A},
+ {0x0B4E, 0x0B55}, {0x0B58, 0x0B5B}, {0x0B5E, 0x0B5E},
+ {0x0B64, 0x0B65}, {0x0B78, 0x0B81}, {0x0B84, 0x0B84},
+ {0x0B8B, 0x0B8D}, {0x0B91, 0x0B91}, {0x0B96, 0x0B98},
+ {0x0B9B, 0x0B9B}, {0x0B9D, 0x0B9D}, {0x0BA0, 0x0BA2},
+ {0x0BA5, 0x0BA7}, {0x0BAB, 0x0BAD}, {0x0BBA, 0x0BBD},
+ {0x0BC3, 0x0BC5}, {0x0BC9, 0x0BC9}, {0x0BCE, 0x0BCF},
+ {0x0BD1, 0x0BD6}, {0x0BD8, 0x0BE5}, {0x0BFB, 0x0BFF},
+ {0x0C04, 0x0C04}, {0x0C0D, 0x0C0D}, {0x0C11, 0x0C11},
+ {0x0C29, 0x0C29}, {0x0C3A, 0x0C3C}, {0x0C45, 0x0C45},
+ {0x0C49, 0x0C49}, {0x0C4E, 0x0C54}, {0x0C57, 0x0C57},
+ {0x0C5B, 0x0C5F}, {0x0C64, 0x0C65}, {0x0C70, 0x0C77},
+ {0x0C84, 0x0C84}, {0x0C8D, 0x0C8D}, {0x0C91, 0x0C91},
+ {0x0CA9, 0x0CA9}, {0x0CB4, 0x0CB4}, {0x0CBA, 0x0CBB},
+ {0x0CC5, 0x0CC5}, {0x0CC9, 0x0CC9}, {0x0CCE, 0x0CD4},
+ {0x0CD7, 0x0CDD}, {0x0CDF, 0x0CDF}, {0x0CE4, 0x0CE5},
+ {0x0CF0, 0x0CF0}, {0x0CF3, 0x0D00}, {0x0D04, 0x0D04},
+ {0x0D0D, 0x0D0D}, {0x0D11, 0x0D11}, {0x0D3B, 0x0D3C},
+ {0x0D45, 0x0D45}, {0x0D49, 0x0D49}, {0x0D50, 0x0D53},
+ {0x0D64, 0x0D65}, {0x0D80, 0x0D81}, {0x0D84, 0x0D84},
+ {0x0D97, 0x0D99}, {0x0DB2, 0x0DB2}, {0x0DBC, 0x0DBC},
+ {0x0DBE, 0x0DBF}, {0x0DC7, 0x0DC9}, {0x0DCB, 0x0DCE},
+ {0x0DD5, 0x0DD5}, {0x0DD7, 0x0DD7}, {0x0DE0, 0x0DE5},
+ {0x0DF0, 0x0DF1}, {0x0DF5, 0x0E00}, {0x0E3B, 0x0E3E},
+ {0x0E5C, 0x0E80}, {0x0E83, 0x0E83}, {0x0E85, 0x0E86},
+ {0x0E89, 0x0E89}, {0x0E8B, 0x0E8C}, {0x0E8E, 0x0E93},
+ {0x0E98, 0x0E98}, {0x0EA0, 0x0EA0}, {0x0EA4, 0x0EA4},
+ {0x0EA6, 0x0EA6}, {0x0EA8, 0x0EA9}, {0x0EAC, 0x0EAC},
+ {0x0EBA, 0x0EBA}, {0x0EBE, 0x0EBF}, {0x0EC5, 0x0EC5},
+ {0x0EC7, 0x0EC7}, {0x0ECE, 0x0ECF}, {0x0EDA, 0x0EDB},
+ {0x0EE0, 0x0EFF}, {0x0F48, 0x0F48}, {0x0F6D, 0x0F70},
+ {0x0F98, 0x0F98}, {0x0FBD, 0x0FBD}, {0x0FCD, 0x0FCD},
+ {0x0FDB, 0x0FFF}, {0x10C6, 0x10C6}, {0x10C8, 0x10CC},
+ {0x10CE, 0x10CF}, {0x1249, 0x1249}, {0x124E, 0x124F},
+ {0x1257, 0x1257}, {0x1259, 0x1259}, {0x125E, 0x125F},
+ {0x1289, 0x1289}, {0x128E, 0x128F}, {0x12B1, 0x12B1},
+ {0x12B6, 0x12B7}, {0x12BF, 0x12BF}, {0x12C1, 0x12C1},
+ {0x12C6, 0x12C7}, {0x12D7, 0x12D7}, {0x1311, 0x1311},
+ {0x1316, 0x1317}, {0x135B, 0x135C}, {0x137D, 0x137F},
+ {0x139A, 0x139F}, {0x13F6, 0x13F7}, {0x13FE, 0x13FF},
+ {0x169D, 0x169F}, {0x16F9, 0x16FF}, {0x170D, 0x170D},
+ {0x1715, 0x171F}, {0x1737, 0x173F}, {0x1754, 0x175F},
+ {0x176D, 0x176D}, {0x1771, 0x1771}, {0x1774, 0x177F},
+ {0x17DE, 0x17DF}, {0x17EA, 0x17EF}, {0x17FA, 0x17FF},
+ {0x180F, 0x180F}, {0x181A, 0x181F}, {0x1878, 0x187F},
+ {0x18AB, 0x18AF}, {0x18F6, 0x18FF}, {0x191F, 0x191F},
+ {0x192C, 0x192F}, {0x193C, 0x193F}, {0x1941, 0x1943},
+ {0x196E, 0x196F}, {0x1975, 0x197F}, {0x19AC, 0x19AF},
+ {0x19CA, 0x19CF}, {0x19DB, 0x19DD}, {0x1A1C, 0x1A1D},
+ {0x1A5F, 0x1A5F}, {0x1A7D, 0x1A7E}, {0x1A8A, 0x1A8F},
+ {0x1A9A, 0x1A9F}, {0x1AAE, 0x1AAF}, {0x1ABF, 0x1AFF},
+ {0x1B4C, 0x1B4F}, {0x1B7D, 0x1B7F}, {0x1BF4, 0x1BFB},
+ {0x1C38, 0x1C3A}, {0x1C4A, 0x1C4C}, {0x1C89, 0x1CBF},
+ {0x1CC8, 0x1CCF}, {0x1CF7, 0x1CF7}, {0x1CFA, 0x1CFF},
+ {0x1DF6, 0x1DFA}, {0x1F16, 0x1F17}, {0x1F1E, 0x1F1F},
+ {0x1F46, 0x1F47}, {0x1F4E, 0x1F4F}, {0x1F58, 0x1F58},
+ {0x1F5A, 0x1F5A}, {0x1F5C, 0x1F5C}, {0x1F5E, 0x1F5E},
+ {0x1F7E, 0x1F7F}, {0x1FB5, 0x1FB5}, {0x1FC5, 0x1FC5},
+ {0x1FD4, 0x1FD5}, {0x1FDC, 0x1FDC}, {0x1FF0, 0x1FF1},
+ {0x1FF5, 0x1FF5}, {0x1FFF, 0x1FFF}, {0x2065, 0x2065},
+ {0x2072, 0x2073}, {0x208F, 0x208F}, {0x209D, 0x209F},
+ {0x20BF, 0x20CF}, {0x20F1, 0x20FF}, {0x218C, 0x218F},
+ {0x23FF, 0x23FF}, {0x2427, 0x243F}, {0x244B, 0x245F},
+ {0x2B74, 0x2B75}, {0x2B96, 0x2B97}, {0x2BBA, 0x2BBC},
+ {0x2BC9, 0x2BC9}, {0x2BD2, 0x2BEB}, {0x2BF0, 0x2BFF},
+ {0x2C2F, 0x2C2F}, {0x2C5F, 0x2C5F}, {0x2CF4, 0x2CF8},
+ {0x2D26, 0x2D26}, {0x2D28, 0x2D2C}, {0x2D2E, 0x2D2F},
+ {0x2D68, 0x2D6E}, {0x2D71, 0x2D7E}, {0x2D97, 0x2D9F},
+ {0x2DA7, 0x2DA7}, {0x2DAF, 0x2DAF}, {0x2DB7, 0x2DB7},
+ {0x2DBF, 0x2DBF}, {0x2DC7, 0x2DC7}, {0x2DCF, 0x2DCF},
+ {0x2DD7, 0x2DD7}, {0x2DDF, 0x2DDF}, {0x2E45, 0x2E7F},
+ {0x2E9A, 0x2E9A}, {0x2EF4, 0x2EFF}, {0x2FD6, 0x2FEF},
+ {0x2FFC, 0x2FFF}, {0x3040, 0x3040}, {0x3097, 0x3098},
+ {0x3100, 0x3104}, {0x312E, 0x3130}, {0x318F, 0x318F},
+ {0x31BB, 0x31BF}, {0x31E4, 0x31EF}, {0x321F, 0x321F},
+ {0x32FF, 0x32FF}, {0x4DB6, 0x4DBF}, {0x9FD6, 0x9FFF},
+ {0xA48D, 0xA48F}, {0xA4C7, 0xA4CF}, {0xA62C, 0xA63F},
+ {0xA6F8, 0xA6FF}, {0xA7AF, 0xA7AF}, {0xA7B8, 0xA7F6},
+ {0xA82C, 0xA82F}, {0xA83A, 0xA83F}, {0xA878, 0xA87F},
+ {0xA8C6, 0xA8CD}, {0xA8DA, 0xA8DF}, {0xA8FE, 0xA8FF},
+ {0xA954, 0xA95E}, {0xA97D, 0xA97F}, {0xA9CE, 0xA9CE},
+ {0xA9DA, 0xA9DD}, {0xA9FF, 0xA9FF}, {0xAA37, 0xAA3F},
+ {0xAA4E, 0xAA4F}, {0xAA5A, 0xAA5B}, {0xAAC3, 0xAADA},
+ {0xAAF7, 0xAB00}, {0xAB07, 0xAB08}, {0xAB0F, 0xAB10},
+ {0xAB17, 0xAB1F}, {0xAB27, 0xAB27}, {0xAB2F, 0xAB2F},
+ {0xAB66, 0xAB6F}, {0xABEE, 0xABEF}, {0xABFA, 0xABFF},
+ {0xD7A4, 0xD7AF}, {0xD7C7, 0xD7CA}, {0xD7FC, 0xD7FF},
+ {0xFA6E, 0xFA6F}, {0xFADA, 0xFAFF}, {0xFB07, 0xFB12},
+ {0xFB18, 0xFB1C}, {0xFB37, 0xFB37}, {0xFB3D, 0xFB3D},
+ {0xFB3F, 0xFB3F}, {0xFB42, 0xFB42}, {0xFB45, 0xFB45},
+ {0xFBC2, 0xFBD2}, {0xFD40, 0xFD4F}, {0xFD90, 0xFD91},
+ {0xFDC8, 0xFDEF}, {0xFDFE, 0xFDFF}, {0xFE1A, 0xFE1F},
+ {0xFE53, 0xFE53}, {0xFE67, 0xFE67}, {0xFE6C, 0xFE6F},
+ {0xFE75, 0xFE75}, {0xFEFD, 0xFEFE}, {0xFF00, 0xFF00},
+ {0xFFBF, 0xFFC1}, {0xFFC8, 0xFFC9}, {0xFFD0, 0xFFD1},
+ {0xFFD8, 0xFFD9}, {0xFFDD, 0xFFDF}, {0xFFE7, 0xFFE7},
+ {0xFFEF, 0xFFF8}, {0xFFFE, 0xFFFF}, {0x1000C, 0x1000C},
+ {0x10027, 0x10027}, {0x1003B, 0x1003B}, {0x1003E, 0x1003E},
+ {0x1004E, 0x1004F}, {0x1005E, 0x1007F}, {0x100FB, 0x100FF},
+ {0x10103, 0x10106}, {0x10134, 0x10136}, {0x1018F, 0x1018F},
+ {0x1019C, 0x1019F}, {0x101A1, 0x101CF}, {0x101FE, 0x1027F},
+ {0x1029D, 0x1029F}, {0x102D1, 0x102DF}, {0x102FC, 0x102FF},
+ {0x10324, 0x1032F}, {0x1034B, 0x1034F}, {0x1037B, 0x1037F},
+ {0x1039E, 0x1039E}, {0x103C4, 0x103C7}, {0x103D6, 0x103FF},
+ {0x1049E, 0x1049F}, {0x104AA, 0x104AF}, {0x104D4, 0x104D7},
+ {0x104FC, 0x104FF}, {0x10528, 0x1052F}, {0x10564, 0x1056E},
+ {0x10570, 0x105FF}, {0x10737, 0x1073F}, {0x10756, 0x1075F},
+ {0x10768, 0x107FF}, {0x10806, 0x10807}, {0x10809, 0x10809},
+ {0x10836, 0x10836}, {0x10839, 0x1083B}, {0x1083D, 0x1083E},
+ {0x10856, 0x10856}, {0x1089F, 0x108A6}, {0x108B0, 0x108DF},
+ {0x108F3, 0x108F3}, {0x108F6, 0x108FA}, {0x1091C, 0x1091E},
+ {0x1093A, 0x1093E}, {0x10940, 0x1097F}, {0x109B8, 0x109BB},
+ {0x109D0, 0x109D1}, {0x10A04, 0x10A04}, {0x10A07, 0x10A0B},
+ {0x10A14, 0x10A14}, {0x10A18, 0x10A18}, {0x10A34, 0x10A37},
+ {0x10A3B, 0x10A3E}, {0x10A48, 0x10A4F}, {0x10A59, 0x10A5F},
+ {0x10AA0, 0x10ABF}, {0x10AE7, 0x10AEA}, {0x10AF7, 0x10AFF},
+ {0x10B36, 0x10B38}, {0x10B56, 0x10B57}, {0x10B73, 0x10B77},
+ {0x10B92, 0x10B98}, {0x10B9D, 0x10BA8}, {0x10BB0, 0x10BFF},
+ {0x10C49, 0x10C7F}, {0x10CB3, 0x10CBF}, {0x10CF3, 0x10CF9},
+ {0x10D00, 0x10E5F}, {0x10E7F, 0x10FFF}, {0x1104E, 0x11051},
+ {0x11070, 0x1107E}, {0x110C2, 0x110CF}, {0x110E9, 0x110EF},
+ {0x110FA, 0x110FF}, {0x11135, 0x11135}, {0x11144, 0x1114F},
+ {0x11177, 0x1117F}, {0x111CE, 0x111CF}, {0x111E0, 0x111E0},
+ {0x111F5, 0x111FF}, {0x11212, 0x11212}, {0x1123F, 0x1127F},
+ {0x11287, 0x11287}, {0x11289, 0x11289}, {0x1128E, 0x1128E},
+ {0x1129E, 0x1129E}, {0x112AA, 0x112AF}, {0x112EB, 0x112EF},
+ {0x112FA, 0x112FF}, {0x11304, 0x11304}, {0x1130D, 0x1130E},
+ {0x11311, 0x11312}, {0x11329, 0x11329}, {0x11331, 0x11331},
+ {0x11334, 0x11334}, {0x1133A, 0x1133B}, {0x11345, 0x11346},
+ {0x11349, 0x1134A}, {0x1134E, 0x1134F}, {0x11351, 0x11356},
+ {0x11358, 0x1135C}, {0x11364, 0x11365}, {0x1136D, 0x1136F},
+ {0x11375, 0x113FF}, {0x1145A, 0x1145A}, {0x1145C, 0x1145C},
+ {0x1145E, 0x1147F}, {0x114C8, 0x114CF}, {0x114DA, 0x1157F},
+ {0x115B6, 0x115B7}, {0x115DE, 0x115FF}, {0x11645, 0x1164F},
+ {0x1165A, 0x1165F}, {0x1166D, 0x1167F}, {0x116B8, 0x116BF},
+ {0x116CA, 0x116FF}, {0x1171A, 0x1171C}, {0x1172C, 0x1172F},
+ {0x11740, 0x1189F}, {0x118F3, 0x118FE}, {0x11900, 0x11ABF},
+ {0x11AF9, 0x11BFF}, {0x11C09, 0x11C09}, {0x11C37, 0x11C37},
+ {0x11C46, 0x11C4F}, {0x11C6D, 0x11C6F}, {0x11C90, 0x11C91},
+ {0x11CA8, 0x11CA8}, {0x11CB7, 0x11FFF}, {0x1239A, 0x123FF},
+ {0x1246F, 0x1246F}, {0x12475, 0x1247F}, {0x12544, 0x12FFF},
+ {0x1342F, 0x143FF}, {0x14647, 0x167FF}, {0x16A39, 0x16A3F},
+ {0x16A5F, 0x16A5F}, {0x16A6A, 0x16A6D}, {0x16A70, 0x16ACF},
+ {0x16AEE, 0x16AEF}, {0x16AF6, 0x16AFF}, {0x16B46, 0x16B4F},
+ {0x16B5A, 0x16B5A}, {0x16B62, 0x16B62}, {0x16B78, 0x16B7C},
+ {0x16B90, 0x16EFF}, {0x16F45, 0x16F4F}, {0x16F7F, 0x16F8E},
+ {0x16FA0, 0x16FDF}, {0x16FE1, 0x16FFF}, {0x187ED, 0x187FF},
+ {0x18AF3, 0x1AFFF}, {0x1B002, 0x1BBFF}, {0x1BC6B, 0x1BC6F},
+ {0x1BC7D, 0x1BC7F}, {0x1BC89, 0x1BC8F}, {0x1BC9A, 0x1BC9B},
+ {0x1BCA4, 0x1CFFF}, {0x1D0F6, 0x1D0FF}, {0x1D127, 0x1D128},
+ {0x1D1E9, 0x1D1FF}, {0x1D246, 0x1D2FF}, {0x1D357, 0x1D35F},
+ {0x1D372, 0x1D3FF}, {0x1D455, 0x1D455}, {0x1D49D, 0x1D49D},
+ {0x1D4A0, 0x1D4A1}, {0x1D4A3, 0x1D4A4}, {0x1D4A7, 0x1D4A8},
+ {0x1D4AD, 0x1D4AD}, {0x1D4BA, 0x1D4BA}, {0x1D4BC, 0x1D4BC},
+ {0x1D4C4, 0x1D4C4}, {0x1D506, 0x1D506}, {0x1D50B, 0x1D50C},
+ {0x1D515, 0x1D515}, {0x1D51D, 0x1D51D}, {0x1D53A, 0x1D53A},
+ {0x1D53F, 0x1D53F}, {0x1D545, 0x1D545}, {0x1D547, 0x1D549},
+ {0x1D551, 0x1D551}, {0x1D6A6, 0x1D6A7}, {0x1D7CC, 0x1D7CD},
+ {0x1DA8C, 0x1DA9A}, {0x1DAA0, 0x1DAA0}, {0x1DAB0, 0x1DFFF},
+ {0x1E007, 0x1E007}, {0x1E019, 0x1E01A}, {0x1E022, 0x1E022},
+ {0x1E025, 0x1E025}, {0x1E02B, 0x1E7FF}, {0x1E8C5, 0x1E8C6},
+ {0x1E8D7, 0x1E8FF}, {0x1E94B, 0x1E94F}, {0x1E95A, 0x1E95D},
+ {0x1E960, 0x1EDFF}, {0x1EE04, 0x1EE04}, {0x1EE20, 0x1EE20},
+ {0x1EE23, 0x1EE23}, {0x1EE25, 0x1EE26}, {0x1EE28, 0x1EE28},
+ {0x1EE33, 0x1EE33}, {0x1EE38, 0x1EE38}, {0x1EE3A, 0x1EE3A},
+ {0x1EE3C, 0x1EE41}, {0x1EE43, 0x1EE46}, {0x1EE48, 0x1EE48},
+ {0x1EE4A, 0x1EE4A}, {0x1EE4C, 0x1EE4C}, {0x1EE50, 0x1EE50},
+ {0x1EE53, 0x1EE53}, {0x1EE55, 0x1EE56}, {0x1EE58, 0x1EE58},
+ {0x1EE5A, 0x1EE5A}, {0x1EE5C, 0x1EE5C}, {0x1EE5E, 0x1EE5E},
+ {0x1EE60, 0x1EE60}, {0x1EE63, 0x1EE63}, {0x1EE65, 0x1EE66},
+ {0x1EE6B, 0x1EE6B}, {0x1EE73, 0x1EE73}, {0x1EE78, 0x1EE78},
+ {0x1EE7D, 0x1EE7D}, {0x1EE7F, 0x1EE7F}, {0x1EE8A, 0x1EE8A},
+ {0x1EE9C, 0x1EEA0}, {0x1EEA4, 0x1EEA4}, {0x1EEAA, 0x1EEAA},
+ {0x1EEBC, 0x1EEEF}, {0x1EEF2, 0x1EFFF}, {0x1F02C, 0x1F02F},
+ {0x1F094, 0x1F09F}, {0x1F0AF, 0x1F0B0}, {0x1F0C0, 0x1F0C0},
+ {0x1F0D0, 0x1F0D0}, {0x1F0F6, 0x1F0FF}, {0x1F10D, 0x1F10F},
+ {0x1F12F, 0x1F12F}, {0x1F16C, 0x1F16F}, {0x1F1AD, 0x1F1E5},
+ {0x1F203, 0x1F20F}, {0x1F23C, 0x1F23F}, {0x1F249, 0x1F24F},
+ {0x1F252, 0x1F2FF}, {0x1F6D3, 0x1F6DF}, {0x1F6ED, 0x1F6EF},
+ {0x1F6F7, 0x1F6FF}, {0x1F774, 0x1F77F}, {0x1F7D5, 0x1F7FF},
+ {0x1F80C, 0x1F80F}, {0x1F848, 0x1F84F}, {0x1F85A, 0x1F85F},
+ {0x1F888, 0x1F88F}, {0x1F8AE, 0x1F90F}, {0x1F91F, 0x1F91F},
+ {0x1F928, 0x1F92F}, {0x1F931, 0x1F932}, {0x1F93F, 0x1F93F},
+ {0x1F94C, 0x1F94F}, {0x1F95F, 0x1F97F}, {0x1F992, 0x1F9BF},
+ {0x1F9C1, 0x1FFFF}, {0x2A6D7, 0x2A6FF}, {0x2B735, 0x2B73F},
+ {0x2B81E, 0x2B81F}, {0x2CEA2, 0x2F7FF}, {0x2FA1E, 0xE0000},
+ {0xE0002, 0xE001F}, {0xE0080, 0xE00FF}, {0xE01F0, 0xEFFFF},
+ {0xFFFFE, 0xFFFFF},
+}
+
+var neutral = table{
+ {0x0000, 0x001F}, {0x007F, 0x007F}, {0x0080, 0x009F},
+ {0x00A0, 0x00A0}, {0x00A9, 0x00A9}, {0x00AB, 0x00AB},
+ {0x00B5, 0x00B5}, {0x00BB, 0x00BB}, {0x00C0, 0x00C5},
+ {0x00C7, 0x00CF}, {0x00D1, 0x00D6}, {0x00D9, 0x00DD},
+ {0x00E2, 0x00E5}, {0x00E7, 0x00E7}, {0x00EB, 0x00EB},
+ {0x00EE, 0x00EF}, {0x00F1, 0x00F1}, {0x00F4, 0x00F6},
+ {0x00FB, 0x00FB}, {0x00FD, 0x00FD}, {0x00FF, 0x00FF},
+ {0x0100, 0x0100}, {0x0102, 0x0110}, {0x0112, 0x0112},
+ {0x0114, 0x011A}, {0x011C, 0x0125}, {0x0128, 0x012A},
+ {0x012C, 0x0130}, {0x0134, 0x0137}, {0x0139, 0x013E},
+ {0x0143, 0x0143}, {0x0145, 0x0147}, {0x014C, 0x014C},
+ {0x014E, 0x0151}, {0x0154, 0x0165}, {0x0168, 0x016A},
+ {0x016C, 0x017F}, {0x0180, 0x01BA}, {0x01BB, 0x01BB},
+ {0x01BC, 0x01BF}, {0x01C0, 0x01C3}, {0x01C4, 0x01CD},
+ {0x01CF, 0x01CF}, {0x01D1, 0x01D1}, {0x01D3, 0x01D3},
+ {0x01D5, 0x01D5}, {0x01D7, 0x01D7}, {0x01D9, 0x01D9},
+ {0x01DB, 0x01DB}, {0x01DD, 0x024F}, {0x0250, 0x0250},
+ {0x0252, 0x0260}, {0x0262, 0x0293}, {0x0294, 0x0294},
+ {0x0295, 0x02AF}, {0x02B0, 0x02C1}, {0x02C2, 0x02C3},
+ {0x02C5, 0x02C5}, {0x02C6, 0x02C6}, {0x02C8, 0x02C8},
+ {0x02CC, 0x02CC}, {0x02CE, 0x02CF}, {0x02D1, 0x02D1},
+ {0x02D2, 0x02D7}, {0x02DC, 0x02DC}, {0x02DE, 0x02DE},
+ {0x02E0, 0x02E4}, {0x02E5, 0x02EB}, {0x02EC, 0x02EC},
+ {0x02ED, 0x02ED}, {0x02EE, 0x02EE}, {0x02EF, 0x02FF},
+ {0x0370, 0x0373}, {0x0374, 0x0374}, {0x0375, 0x0375},
+ {0x0376, 0x0377}, {0x037A, 0x037A}, {0x037B, 0x037D},
+ {0x037E, 0x037E}, {0x037F, 0x037F}, {0x0384, 0x0385},
+ {0x0386, 0x0386}, {0x0387, 0x0387}, {0x0388, 0x038A},
+ {0x038C, 0x038C}, {0x038E, 0x0390}, {0x03AA, 0x03B0},
+ {0x03C2, 0x03C2}, {0x03CA, 0x03F5}, {0x03F6, 0x03F6},
+ {0x03F7, 0x03FF}, {0x0400, 0x0400}, {0x0402, 0x040F},
+ {0x0450, 0x0450}, {0x0452, 0x0481}, {0x0482, 0x0482},
+ {0x0483, 0x0487}, {0x0488, 0x0489}, {0x048A, 0x04FF},
+ {0x0500, 0x052F}, {0x0531, 0x0556}, {0x0559, 0x0559},
+ {0x055A, 0x055F}, {0x0561, 0x0587}, {0x0589, 0x0589},
+ {0x058A, 0x058A}, {0x058D, 0x058E}, {0x058F, 0x058F},
+ {0x0591, 0x05BD}, {0x05BE, 0x05BE}, {0x05BF, 0x05BF},
+ {0x05C0, 0x05C0}, {0x05C1, 0x05C2}, {0x05C3, 0x05C3},
+ {0x05C4, 0x05C5}, {0x05C6, 0x05C6}, {0x05C7, 0x05C7},
+ {0x05D0, 0x05EA}, {0x05F0, 0x05F2}, {0x05F3, 0x05F4},
+ {0x0600, 0x0605}, {0x0606, 0x0608}, {0x0609, 0x060A},
+ {0x060B, 0x060B}, {0x060C, 0x060D}, {0x060E, 0x060F},
+ {0x0610, 0x061A}, {0x061B, 0x061B}, {0x061C, 0x061C},
+ {0x061E, 0x061F}, {0x0620, 0x063F}, {0x0640, 0x0640},
+ {0x0641, 0x064A}, {0x064B, 0x065F}, {0x0660, 0x0669},
+ {0x066A, 0x066D}, {0x066E, 0x066F}, {0x0670, 0x0670},
+ {0x0671, 0x06D3}, {0x06D4, 0x06D4}, {0x06D5, 0x06D5},
+ {0x06D6, 0x06DC}, {0x06DD, 0x06DD}, {0x06DE, 0x06DE},
+ {0x06DF, 0x06E4}, {0x06E5, 0x06E6}, {0x06E7, 0x06E8},
+ {0x06E9, 0x06E9}, {0x06EA, 0x06ED}, {0x06EE, 0x06EF},
+ {0x06F0, 0x06F9}, {0x06FA, 0x06FC}, {0x06FD, 0x06FE},
+ {0x06FF, 0x06FF}, {0x0700, 0x070D}, {0x070F, 0x070F},
+ {0x0710, 0x0710}, {0x0711, 0x0711}, {0x0712, 0x072F},
+ {0x0730, 0x074A}, {0x074D, 0x074F}, {0x0750, 0x077F},
+ {0x0780, 0x07A5}, {0x07A6, 0x07B0}, {0x07B1, 0x07B1},
+ {0x07C0, 0x07C9}, {0x07CA, 0x07EA}, {0x07EB, 0x07F3},
+ {0x07F4, 0x07F5}, {0x07F6, 0x07F6}, {0x07F7, 0x07F9},
+ {0x07FA, 0x07FA}, {0x0800, 0x0815}, {0x0816, 0x0819},
+ {0x081A, 0x081A}, {0x081B, 0x0823}, {0x0824, 0x0824},
+ {0x0825, 0x0827}, {0x0828, 0x0828}, {0x0829, 0x082D},
+ {0x0830, 0x083E}, {0x0840, 0x0858}, {0x0859, 0x085B},
+ {0x085E, 0x085E}, {0x08A0, 0x08B4}, {0x08B6, 0x08BD},
+ {0x08D4, 0x08E1}, {0x08E2, 0x08E2}, {0x08E3, 0x08FF},
+ {0x0900, 0x0902}, {0x0903, 0x0903}, {0x0904, 0x0939},
+ {0x093A, 0x093A}, {0x093B, 0x093B}, {0x093C, 0x093C},
+ {0x093D, 0x093D}, {0x093E, 0x0940}, {0x0941, 0x0948},
+ {0x0949, 0x094C}, {0x094D, 0x094D}, {0x094E, 0x094F},
+ {0x0950, 0x0950}, {0x0951, 0x0957}, {0x0958, 0x0961},
+ {0x0962, 0x0963}, {0x0964, 0x0965}, {0x0966, 0x096F},
+ {0x0970, 0x0970}, {0x0971, 0x0971}, {0x0972, 0x097F},
+ {0x0980, 0x0980}, {0x0981, 0x0981}, {0x0982, 0x0983},
+ {0x0985, 0x098C}, {0x098F, 0x0990}, {0x0993, 0x09A8},
+ {0x09AA, 0x09B0}, {0x09B2, 0x09B2}, {0x09B6, 0x09B9},
+ {0x09BC, 0x09BC}, {0x09BD, 0x09BD}, {0x09BE, 0x09C0},
+ {0x09C1, 0x09C4}, {0x09C7, 0x09C8}, {0x09CB, 0x09CC},
+ {0x09CD, 0x09CD}, {0x09CE, 0x09CE}, {0x09D7, 0x09D7},
+ {0x09DC, 0x09DD}, {0x09DF, 0x09E1}, {0x09E2, 0x09E3},
+ {0x09E6, 0x09EF}, {0x09F0, 0x09F1}, {0x09F2, 0x09F3},
+ {0x09F4, 0x09F9}, {0x09FA, 0x09FA}, {0x09FB, 0x09FB},
+ {0x0A01, 0x0A02}, {0x0A03, 0x0A03}, {0x0A05, 0x0A0A},
+ {0x0A0F, 0x0A10}, {0x0A13, 0x0A28}, {0x0A2A, 0x0A30},
+ {0x0A32, 0x0A33}, {0x0A35, 0x0A36}, {0x0A38, 0x0A39},
+ {0x0A3C, 0x0A3C}, {0x0A3E, 0x0A40}, {0x0A41, 0x0A42},
+ {0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A51, 0x0A51},
+ {0x0A59, 0x0A5C}, {0x0A5E, 0x0A5E}, {0x0A66, 0x0A6F},
+ {0x0A70, 0x0A71}, {0x0A72, 0x0A74}, {0x0A75, 0x0A75},
+ {0x0A81, 0x0A82}, {0x0A83, 0x0A83}, {0x0A85, 0x0A8D},
+ {0x0A8F, 0x0A91}, {0x0A93, 0x0AA8}, {0x0AAA, 0x0AB0},
+ {0x0AB2, 0x0AB3}, {0x0AB5, 0x0AB9}, {0x0ABC, 0x0ABC},
+ {0x0ABD, 0x0ABD}, {0x0ABE, 0x0AC0}, {0x0AC1, 0x0AC5},
+ {0x0AC7, 0x0AC8}, {0x0AC9, 0x0AC9}, {0x0ACB, 0x0ACC},
+ {0x0ACD, 0x0ACD}, {0x0AD0, 0x0AD0}, {0x0AE0, 0x0AE1},
+ {0x0AE2, 0x0AE3}, {0x0AE6, 0x0AEF}, {0x0AF0, 0x0AF0},
+ {0x0AF1, 0x0AF1}, {0x0AF9, 0x0AF9}, {0x0B01, 0x0B01},
+ {0x0B02, 0x0B03}, {0x0B05, 0x0B0C}, {0x0B0F, 0x0B10},
+ {0x0B13, 0x0B28}, {0x0B2A, 0x0B30}, {0x0B32, 0x0B33},
+ {0x0B35, 0x0B39}, {0x0B3C, 0x0B3C}, {0x0B3D, 0x0B3D},
+ {0x0B3E, 0x0B3E}, {0x0B3F, 0x0B3F}, {0x0B40, 0x0B40},
+ {0x0B41, 0x0B44}, {0x0B47, 0x0B48}, {0x0B4B, 0x0B4C},
+ {0x0B4D, 0x0B4D}, {0x0B56, 0x0B56}, {0x0B57, 0x0B57},
+ {0x0B5C, 0x0B5D}, {0x0B5F, 0x0B61}, {0x0B62, 0x0B63},
+ {0x0B66, 0x0B6F}, {0x0B70, 0x0B70}, {0x0B71, 0x0B71},
+ {0x0B72, 0x0B77}, {0x0B82, 0x0B82}, {0x0B83, 0x0B83},
+ {0x0B85, 0x0B8A}, {0x0B8E, 0x0B90}, {0x0B92, 0x0B95},
+ {0x0B99, 0x0B9A}, {0x0B9C, 0x0B9C}, {0x0B9E, 0x0B9F},
+ {0x0BA3, 0x0BA4}, {0x0BA8, 0x0BAA}, {0x0BAE, 0x0BB9},
+ {0x0BBE, 0x0BBF}, {0x0BC0, 0x0BC0}, {0x0BC1, 0x0BC2},
+ {0x0BC6, 0x0BC8}, {0x0BCA, 0x0BCC}, {0x0BCD, 0x0BCD},
+ {0x0BD0, 0x0BD0}, {0x0BD7, 0x0BD7}, {0x0BE6, 0x0BEF},
+ {0x0BF0, 0x0BF2}, {0x0BF3, 0x0BF8}, {0x0BF9, 0x0BF9},
+ {0x0BFA, 0x0BFA}, {0x0C00, 0x0C00}, {0x0C01, 0x0C03},
+ {0x0C05, 0x0C0C}, {0x0C0E, 0x0C10}, {0x0C12, 0x0C28},
+ {0x0C2A, 0x0C39}, {0x0C3D, 0x0C3D}, {0x0C3E, 0x0C40},
+ {0x0C41, 0x0C44}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D},
+ {0x0C55, 0x0C56}, {0x0C58, 0x0C5A}, {0x0C60, 0x0C61},
+ {0x0C62, 0x0C63}, {0x0C66, 0x0C6F}, {0x0C78, 0x0C7E},
+ {0x0C7F, 0x0C7F}, {0x0C80, 0x0C80}, {0x0C81, 0x0C81},
+ {0x0C82, 0x0C83}, {0x0C85, 0x0C8C}, {0x0C8E, 0x0C90},
+ {0x0C92, 0x0CA8}, {0x0CAA, 0x0CB3}, {0x0CB5, 0x0CB9},
+ {0x0CBC, 0x0CBC}, {0x0CBD, 0x0CBD}, {0x0CBE, 0x0CBE},
+ {0x0CBF, 0x0CBF}, {0x0CC0, 0x0CC4}, {0x0CC6, 0x0CC6},
+ {0x0CC7, 0x0CC8}, {0x0CCA, 0x0CCB}, {0x0CCC, 0x0CCD},
+ {0x0CD5, 0x0CD6}, {0x0CDE, 0x0CDE}, {0x0CE0, 0x0CE1},
+ {0x0CE2, 0x0CE3}, {0x0CE6, 0x0CEF}, {0x0CF1, 0x0CF2},
+ {0x0D01, 0x0D01}, {0x0D02, 0x0D03}, {0x0D05, 0x0D0C},
+ {0x0D0E, 0x0D10}, {0x0D12, 0x0D3A}, {0x0D3D, 0x0D3D},
+ {0x0D3E, 0x0D40}, {0x0D41, 0x0D44}, {0x0D46, 0x0D48},
+ {0x0D4A, 0x0D4C}, {0x0D4D, 0x0D4D}, {0x0D4E, 0x0D4E},
+ {0x0D4F, 0x0D4F}, {0x0D54, 0x0D56}, {0x0D57, 0x0D57},
+ {0x0D58, 0x0D5E}, {0x0D5F, 0x0D61}, {0x0D62, 0x0D63},
+ {0x0D66, 0x0D6F}, {0x0D70, 0x0D78}, {0x0D79, 0x0D79},
+ {0x0D7A, 0x0D7F}, {0x0D82, 0x0D83}, {0x0D85, 0x0D96},
+ {0x0D9A, 0x0DB1}, {0x0DB3, 0x0DBB}, {0x0DBD, 0x0DBD},
+ {0x0DC0, 0x0DC6}, {0x0DCA, 0x0DCA}, {0x0DCF, 0x0DD1},
+ {0x0DD2, 0x0DD4}, {0x0DD6, 0x0DD6}, {0x0DD8, 0x0DDF},
+ {0x0DE6, 0x0DEF}, {0x0DF2, 0x0DF3}, {0x0DF4, 0x0DF4},
+ {0x0E01, 0x0E30}, {0x0E31, 0x0E31}, {0x0E32, 0x0E33},
+ {0x0E34, 0x0E3A}, {0x0E3F, 0x0E3F}, {0x0E40, 0x0E45},
+ {0x0E46, 0x0E46}, {0x0E47, 0x0E4E}, {0x0E4F, 0x0E4F},
+ {0x0E50, 0x0E59}, {0x0E5A, 0x0E5B}, {0x0E81, 0x0E82},
+ {0x0E84, 0x0E84}, {0x0E87, 0x0E88}, {0x0E8A, 0x0E8A},
+ {0x0E8D, 0x0E8D}, {0x0E94, 0x0E97}, {0x0E99, 0x0E9F},
+ {0x0EA1, 0x0EA3}, {0x0EA5, 0x0EA5}, {0x0EA7, 0x0EA7},
+ {0x0EAA, 0x0EAB}, {0x0EAD, 0x0EB0}, {0x0EB1, 0x0EB1},
+ {0x0EB2, 0x0EB3}, {0x0EB4, 0x0EB9}, {0x0EBB, 0x0EBC},
+ {0x0EBD, 0x0EBD}, {0x0EC0, 0x0EC4}, {0x0EC6, 0x0EC6},
+ {0x0EC8, 0x0ECD}, {0x0ED0, 0x0ED9}, {0x0EDC, 0x0EDF},
+ {0x0F00, 0x0F00}, {0x0F01, 0x0F03}, {0x0F04, 0x0F12},
+ {0x0F13, 0x0F13}, {0x0F14, 0x0F14}, {0x0F15, 0x0F17},
+ {0x0F18, 0x0F19}, {0x0F1A, 0x0F1F}, {0x0F20, 0x0F29},
+ {0x0F2A, 0x0F33}, {0x0F34, 0x0F34}, {0x0F35, 0x0F35},
+ {0x0F36, 0x0F36}, {0x0F37, 0x0F37}, {0x0F38, 0x0F38},
+ {0x0F39, 0x0F39}, {0x0F3A, 0x0F3A}, {0x0F3B, 0x0F3B},
+ {0x0F3C, 0x0F3C}, {0x0F3D, 0x0F3D}, {0x0F3E, 0x0F3F},
+ {0x0F40, 0x0F47}, {0x0F49, 0x0F6C}, {0x0F71, 0x0F7E},
+ {0x0F7F, 0x0F7F}, {0x0F80, 0x0F84}, {0x0F85, 0x0F85},
+ {0x0F86, 0x0F87}, {0x0F88, 0x0F8C}, {0x0F8D, 0x0F97},
+ {0x0F99, 0x0FBC}, {0x0FBE, 0x0FC5}, {0x0FC6, 0x0FC6},
+ {0x0FC7, 0x0FCC}, {0x0FCE, 0x0FCF}, {0x0FD0, 0x0FD4},
+ {0x0FD5, 0x0FD8}, {0x0FD9, 0x0FDA}, {0x1000, 0x102A},
+ {0x102B, 0x102C}, {0x102D, 0x1030}, {0x1031, 0x1031},
+ {0x1032, 0x1037}, {0x1038, 0x1038}, {0x1039, 0x103A},
+ {0x103B, 0x103C}, {0x103D, 0x103E}, {0x103F, 0x103F},
+ {0x1040, 0x1049}, {0x104A, 0x104F}, {0x1050, 0x1055},
+ {0x1056, 0x1057}, {0x1058, 0x1059}, {0x105A, 0x105D},
+ {0x105E, 0x1060}, {0x1061, 0x1061}, {0x1062, 0x1064},
+ {0x1065, 0x1066}, {0x1067, 0x106D}, {0x106E, 0x1070},
+ {0x1071, 0x1074}, {0x1075, 0x1081}, {0x1082, 0x1082},
+ {0x1083, 0x1084}, {0x1085, 0x1086}, {0x1087, 0x108C},
+ {0x108D, 0x108D}, {0x108E, 0x108E}, {0x108F, 0x108F},
+ {0x1090, 0x1099}, {0x109A, 0x109C}, {0x109D, 0x109D},
+ {0x109E, 0x109F}, {0x10A0, 0x10C5}, {0x10C7, 0x10C7},
+ {0x10CD, 0x10CD}, {0x10D0, 0x10FA}, {0x10FB, 0x10FB},
+ {0x10FC, 0x10FC}, {0x10FD, 0x10FF}, {0x1160, 0x11FF},
+ {0x1200, 0x1248}, {0x124A, 0x124D}, {0x1250, 0x1256},
+ {0x1258, 0x1258}, {0x125A, 0x125D}, {0x1260, 0x1288},
+ {0x128A, 0x128D}, {0x1290, 0x12B0}, {0x12B2, 0x12B5},
+ {0x12B8, 0x12BE}, {0x12C0, 0x12C0}, {0x12C2, 0x12C5},
+ {0x12C8, 0x12D6}, {0x12D8, 0x1310}, {0x1312, 0x1315},
+ {0x1318, 0x135A}, {0x135D, 0x135F}, {0x1360, 0x1368},
+ {0x1369, 0x137C}, {0x1380, 0x138F}, {0x1390, 0x1399},
+ {0x13A0, 0x13F5}, {0x13F8, 0x13FD}, {0x1400, 0x1400},
+ {0x1401, 0x166C}, {0x166D, 0x166E}, {0x166F, 0x167F},
+ {0x1680, 0x1680}, {0x1681, 0x169A}, {0x169B, 0x169B},
+ {0x169C, 0x169C}, {0x16A0, 0x16EA}, {0x16EB, 0x16ED},
+ {0x16EE, 0x16F0}, {0x16F1, 0x16F8}, {0x1700, 0x170C},
+ {0x170E, 0x1711}, {0x1712, 0x1714}, {0x1720, 0x1731},
+ {0x1732, 0x1734}, {0x1735, 0x1736}, {0x1740, 0x1751},
+ {0x1752, 0x1753}, {0x1760, 0x176C}, {0x176E, 0x1770},
+ {0x1772, 0x1773}, {0x1780, 0x17B3}, {0x17B4, 0x17B5},
+ {0x17B6, 0x17B6}, {0x17B7, 0x17BD}, {0x17BE, 0x17C5},
+ {0x17C6, 0x17C6}, {0x17C7, 0x17C8}, {0x17C9, 0x17D3},
+ {0x17D4, 0x17D6}, {0x17D7, 0x17D7}, {0x17D8, 0x17DA},
+ {0x17DB, 0x17DB}, {0x17DC, 0x17DC}, {0x17DD, 0x17DD},
+ {0x17E0, 0x17E9}, {0x17F0, 0x17F9}, {0x1800, 0x1805},
+ {0x1806, 0x1806}, {0x1807, 0x180A}, {0x180B, 0x180D},
+ {0x180E, 0x180E}, {0x1810, 0x1819}, {0x1820, 0x1842},
+ {0x1843, 0x1843}, {0x1844, 0x1877}, {0x1880, 0x1884},
+ {0x1885, 0x1886}, {0x1887, 0x18A8}, {0x18A9, 0x18A9},
+ {0x18AA, 0x18AA}, {0x18B0, 0x18F5}, {0x1900, 0x191E},
+ {0x1920, 0x1922}, {0x1923, 0x1926}, {0x1927, 0x1928},
+ {0x1929, 0x192B}, {0x1930, 0x1931}, {0x1932, 0x1932},
+ {0x1933, 0x1938}, {0x1939, 0x193B}, {0x1940, 0x1940},
+ {0x1944, 0x1945}, {0x1946, 0x194F}, {0x1950, 0x196D},
+ {0x1970, 0x1974}, {0x1980, 0x19AB}, {0x19B0, 0x19C9},
+ {0x19D0, 0x19D9}, {0x19DA, 0x19DA}, {0x19DE, 0x19DF},
+ {0x19E0, 0x19FF}, {0x1A00, 0x1A16}, {0x1A17, 0x1A18},
+ {0x1A19, 0x1A1A}, {0x1A1B, 0x1A1B}, {0x1A1E, 0x1A1F},
+ {0x1A20, 0x1A54}, {0x1A55, 0x1A55}, {0x1A56, 0x1A56},
+ {0x1A57, 0x1A57}, {0x1A58, 0x1A5E}, {0x1A60, 0x1A60},
+ {0x1A61, 0x1A61}, {0x1A62, 0x1A62}, {0x1A63, 0x1A64},
+ {0x1A65, 0x1A6C}, {0x1A6D, 0x1A72}, {0x1A73, 0x1A7C},
+ {0x1A7F, 0x1A7F}, {0x1A80, 0x1A89}, {0x1A90, 0x1A99},
+ {0x1AA0, 0x1AA6}, {0x1AA7, 0x1AA7}, {0x1AA8, 0x1AAD},
+ {0x1AB0, 0x1ABD}, {0x1ABE, 0x1ABE}, {0x1B00, 0x1B03},
+ {0x1B04, 0x1B04}, {0x1B05, 0x1B33}, {0x1B34, 0x1B34},
+ {0x1B35, 0x1B35}, {0x1B36, 0x1B3A}, {0x1B3B, 0x1B3B},
+ {0x1B3C, 0x1B3C}, {0x1B3D, 0x1B41}, {0x1B42, 0x1B42},
+ {0x1B43, 0x1B44}, {0x1B45, 0x1B4B}, {0x1B50, 0x1B59},
+ {0x1B5A, 0x1B60}, {0x1B61, 0x1B6A}, {0x1B6B, 0x1B73},
+ {0x1B74, 0x1B7C}, {0x1B80, 0x1B81}, {0x1B82, 0x1B82},
+ {0x1B83, 0x1BA0}, {0x1BA1, 0x1BA1}, {0x1BA2, 0x1BA5},
+ {0x1BA6, 0x1BA7}, {0x1BA8, 0x1BA9}, {0x1BAA, 0x1BAA},
+ {0x1BAB, 0x1BAD}, {0x1BAE, 0x1BAF}, {0x1BB0, 0x1BB9},
+ {0x1BBA, 0x1BBF}, {0x1BC0, 0x1BE5}, {0x1BE6, 0x1BE6},
+ {0x1BE7, 0x1BE7}, {0x1BE8, 0x1BE9}, {0x1BEA, 0x1BEC},
+ {0x1BED, 0x1BED}, {0x1BEE, 0x1BEE}, {0x1BEF, 0x1BF1},
+ {0x1BF2, 0x1BF3}, {0x1BFC, 0x1BFF}, {0x1C00, 0x1C23},
+ {0x1C24, 0x1C2B}, {0x1C2C, 0x1C33}, {0x1C34, 0x1C35},
+ {0x1C36, 0x1C37}, {0x1C3B, 0x1C3F}, {0x1C40, 0x1C49},
+ {0x1C4D, 0x1C4F}, {0x1C50, 0x1C59}, {0x1C5A, 0x1C77},
+ {0x1C78, 0x1C7D}, {0x1C7E, 0x1C7F}, {0x1C80, 0x1C88},
+ {0x1CC0, 0x1CC7}, {0x1CD0, 0x1CD2}, {0x1CD3, 0x1CD3},
+ {0x1CD4, 0x1CE0}, {0x1CE1, 0x1CE1}, {0x1CE2, 0x1CE8},
+ {0x1CE9, 0x1CEC}, {0x1CED, 0x1CED}, {0x1CEE, 0x1CF1},
+ {0x1CF2, 0x1CF3}, {0x1CF4, 0x1CF4}, {0x1CF5, 0x1CF6},
+ {0x1CF8, 0x1CF9}, {0x1D00, 0x1D2B}, {0x1D2C, 0x1D6A},
+ {0x1D6B, 0x1D77}, {0x1D78, 0x1D78}, {0x1D79, 0x1D7F},
+ {0x1D80, 0x1D9A}, {0x1D9B, 0x1DBF}, {0x1DC0, 0x1DF5},
+ {0x1DFB, 0x1DFF}, {0x1E00, 0x1EFF}, {0x1F00, 0x1F15},
+ {0x1F18, 0x1F1D}, {0x1F20, 0x1F45}, {0x1F48, 0x1F4D},
+ {0x1F50, 0x1F57}, {0x1F59, 0x1F59}, {0x1F5B, 0x1F5B},
+ {0x1F5D, 0x1F5D}, {0x1F5F, 0x1F7D}, {0x1F80, 0x1FB4},
+ {0x1FB6, 0x1FBC}, {0x1FBD, 0x1FBD}, {0x1FBE, 0x1FBE},
+ {0x1FBF, 0x1FC1}, {0x1FC2, 0x1FC4}, {0x1FC6, 0x1FCC},
+ {0x1FCD, 0x1FCF}, {0x1FD0, 0x1FD3}, {0x1FD6, 0x1FDB},
+ {0x1FDD, 0x1FDF}, {0x1FE0, 0x1FEC}, {0x1FED, 0x1FEF},
+ {0x1FF2, 0x1FF4}, {0x1FF6, 0x1FFC}, {0x1FFD, 0x1FFE},
+ {0x2000, 0x200A}, {0x200B, 0x200F}, {0x2011, 0x2012},
+ {0x2017, 0x2017}, {0x201A, 0x201A}, {0x201B, 0x201B},
+ {0x201E, 0x201E}, {0x201F, 0x201F}, {0x2023, 0x2023},
+ {0x2028, 0x2028}, {0x2029, 0x2029}, {0x202A, 0x202E},
+ {0x202F, 0x202F}, {0x2031, 0x2031}, {0x2034, 0x2034},
+ {0x2036, 0x2038}, {0x2039, 0x2039}, {0x203A, 0x203A},
+ {0x203C, 0x203D}, {0x203F, 0x2040}, {0x2041, 0x2043},
+ {0x2044, 0x2044}, {0x2045, 0x2045}, {0x2046, 0x2046},
+ {0x2047, 0x2051}, {0x2052, 0x2052}, {0x2053, 0x2053},
+ {0x2054, 0x2054}, {0x2055, 0x205E}, {0x205F, 0x205F},
+ {0x2060, 0x2064}, {0x2066, 0x206F}, {0x2070, 0x2070},
+ {0x2071, 0x2071}, {0x2075, 0x2079}, {0x207A, 0x207C},
+ {0x207D, 0x207D}, {0x207E, 0x207E}, {0x2080, 0x2080},
+ {0x2085, 0x2089}, {0x208A, 0x208C}, {0x208D, 0x208D},
+ {0x208E, 0x208E}, {0x2090, 0x209C}, {0x20A0, 0x20A8},
+ {0x20AA, 0x20AB}, {0x20AD, 0x20BE}, {0x20D0, 0x20DC},
+ {0x20DD, 0x20E0}, {0x20E1, 0x20E1}, {0x20E2, 0x20E4},
+ {0x20E5, 0x20F0}, {0x2100, 0x2101}, {0x2102, 0x2102},
+ {0x2104, 0x2104}, {0x2106, 0x2106}, {0x2107, 0x2107},
+ {0x2108, 0x2108}, {0x210A, 0x2112}, {0x2114, 0x2114},
+ {0x2115, 0x2115}, {0x2117, 0x2117}, {0x2118, 0x2118},
+ {0x2119, 0x211D}, {0x211E, 0x2120}, {0x2123, 0x2123},
+ {0x2124, 0x2124}, {0x2125, 0x2125}, {0x2127, 0x2127},
+ {0x2128, 0x2128}, {0x2129, 0x2129}, {0x212A, 0x212A},
+ {0x212C, 0x212D}, {0x212E, 0x212E}, {0x212F, 0x2134},
+ {0x2135, 0x2138}, {0x2139, 0x2139}, {0x213A, 0x213B},
+ {0x213C, 0x213F}, {0x2140, 0x2144}, {0x2145, 0x2149},
+ {0x214A, 0x214A}, {0x214B, 0x214B}, {0x214C, 0x214D},
+ {0x214E, 0x214E}, {0x214F, 0x214F}, {0x2150, 0x2152},
+ {0x2155, 0x215A}, {0x215F, 0x215F}, {0x216C, 0x216F},
+ {0x217A, 0x2182}, {0x2183, 0x2184}, {0x2185, 0x2188},
+ {0x218A, 0x218B}, {0x219A, 0x219B}, {0x219C, 0x219F},
+ {0x21A0, 0x21A0}, {0x21A1, 0x21A2}, {0x21A3, 0x21A3},
+ {0x21A4, 0x21A5}, {0x21A6, 0x21A6}, {0x21A7, 0x21AD},
+ {0x21AE, 0x21AE}, {0x21AF, 0x21B7}, {0x21BA, 0x21CD},
+ {0x21CE, 0x21CF}, {0x21D0, 0x21D1}, {0x21D3, 0x21D3},
+ {0x21D5, 0x21E6}, {0x21E8, 0x21F3}, {0x21F4, 0x21FF},
+ {0x2201, 0x2201}, {0x2204, 0x2206}, {0x2209, 0x220A},
+ {0x220C, 0x220E}, {0x2210, 0x2210}, {0x2212, 0x2214},
+ {0x2216, 0x2219}, {0x221B, 0x221C}, {0x2221, 0x2222},
+ {0x2224, 0x2224}, {0x2226, 0x2226}, {0x222D, 0x222D},
+ {0x222F, 0x2233}, {0x2238, 0x223B}, {0x223E, 0x2247},
+ {0x2249, 0x224B}, {0x224D, 0x2251}, {0x2253, 0x225F},
+ {0x2262, 0x2263}, {0x2268, 0x2269}, {0x226C, 0x226D},
+ {0x2270, 0x2281}, {0x2284, 0x2285}, {0x2288, 0x2294},
+ {0x2296, 0x2298}, {0x229A, 0x22A4}, {0x22A6, 0x22BE},
+ {0x22C0, 0x22FF}, {0x2300, 0x2307}, {0x2308, 0x2308},
+ {0x2309, 0x2309}, {0x230A, 0x230A}, {0x230B, 0x230B},
+ {0x230C, 0x2311}, {0x2313, 0x2319}, {0x231C, 0x231F},
+ {0x2320, 0x2321}, {0x2322, 0x2328}, {0x232B, 0x237B},
+ {0x237C, 0x237C}, {0x237D, 0x239A}, {0x239B, 0x23B3},
+ {0x23B4, 0x23DB}, {0x23DC, 0x23E1}, {0x23E2, 0x23E8},
+ {0x23ED, 0x23EF}, {0x23F1, 0x23F2}, {0x23F4, 0x23FE},
+ {0x2400, 0x2426}, {0x2440, 0x244A}, {0x24EA, 0x24EA},
+ {0x254C, 0x254F}, {0x2574, 0x257F}, {0x2590, 0x2591},
+ {0x2596, 0x259F}, {0x25A2, 0x25A2}, {0x25AA, 0x25B1},
+ {0x25B4, 0x25B5}, {0x25B8, 0x25BB}, {0x25BE, 0x25BF},
+ {0x25C2, 0x25C5}, {0x25C9, 0x25CA}, {0x25CC, 0x25CD},
+ {0x25D2, 0x25E1}, {0x25E6, 0x25EE}, {0x25F0, 0x25F7},
+ {0x25F8, 0x25FC}, {0x25FF, 0x25FF}, {0x2600, 0x2604},
+ {0x2607, 0x2608}, {0x260A, 0x260D}, {0x2610, 0x2613},
+ {0x2616, 0x261B}, {0x261D, 0x261D}, {0x261F, 0x263F},
+ {0x2641, 0x2641}, {0x2643, 0x2647}, {0x2654, 0x265F},
+ {0x2662, 0x2662}, {0x2666, 0x2666}, {0x266B, 0x266B},
+ {0x266E, 0x266E}, {0x2670, 0x267E}, {0x2680, 0x2692},
+ {0x2694, 0x269D}, {0x26A0, 0x26A0}, {0x26A2, 0x26A9},
+ {0x26AC, 0x26BC}, {0x26C0, 0x26C3}, {0x26E2, 0x26E2},
+ {0x26E4, 0x26E7}, {0x2700, 0x2704}, {0x2706, 0x2709},
+ {0x270C, 0x2727}, {0x2729, 0x273C}, {0x273E, 0x274B},
+ {0x274D, 0x274D}, {0x274F, 0x2752}, {0x2756, 0x2756},
+ {0x2758, 0x2767}, {0x2768, 0x2768}, {0x2769, 0x2769},
+ {0x276A, 0x276A}, {0x276B, 0x276B}, {0x276C, 0x276C},
+ {0x276D, 0x276D}, {0x276E, 0x276E}, {0x276F, 0x276F},
+ {0x2770, 0x2770}, {0x2771, 0x2771}, {0x2772, 0x2772},
+ {0x2773, 0x2773}, {0x2774, 0x2774}, {0x2775, 0x2775},
+ {0x2780, 0x2793}, {0x2794, 0x2794}, {0x2798, 0x27AF},
+ {0x27B1, 0x27BE}, {0x27C0, 0x27C4}, {0x27C5, 0x27C5},
+ {0x27C6, 0x27C6}, {0x27C7, 0x27E5}, {0x27EE, 0x27EE},
+ {0x27EF, 0x27EF}, {0x27F0, 0x27FF}, {0x2800, 0x28FF},
+ {0x2900, 0x297F}, {0x2980, 0x2982}, {0x2983, 0x2983},
+ {0x2984, 0x2984}, {0x2987, 0x2987}, {0x2988, 0x2988},
+ {0x2989, 0x2989}, {0x298A, 0x298A}, {0x298B, 0x298B},
+ {0x298C, 0x298C}, {0x298D, 0x298D}, {0x298E, 0x298E},
+ {0x298F, 0x298F}, {0x2990, 0x2990}, {0x2991, 0x2991},
+ {0x2992, 0x2992}, {0x2993, 0x2993}, {0x2994, 0x2994},
+ {0x2995, 0x2995}, {0x2996, 0x2996}, {0x2997, 0x2997},
+ {0x2998, 0x2998}, {0x2999, 0x29D7}, {0x29D8, 0x29D8},
+ {0x29D9, 0x29D9}, {0x29DA, 0x29DA}, {0x29DB, 0x29DB},
+ {0x29DC, 0x29FB}, {0x29FC, 0x29FC}, {0x29FD, 0x29FD},
+ {0x29FE, 0x29FF}, {0x2A00, 0x2AFF}, {0x2B00, 0x2B1A},
+ {0x2B1D, 0x2B2F}, {0x2B30, 0x2B44}, {0x2B45, 0x2B46},
+ {0x2B47, 0x2B4C}, {0x2B4D, 0x2B4F}, {0x2B51, 0x2B54},
+ {0x2B5A, 0x2B73}, {0x2B76, 0x2B95}, {0x2B98, 0x2BB9},
+ {0x2BBD, 0x2BC8}, {0x2BCA, 0x2BD1}, {0x2BEC, 0x2BEF},
+ {0x2C00, 0x2C2E}, {0x2C30, 0x2C5E}, {0x2C60, 0x2C7B},
+ {0x2C7C, 0x2C7D}, {0x2C7E, 0x2C7F}, {0x2C80, 0x2CE4},
+ {0x2CE5, 0x2CEA}, {0x2CEB, 0x2CEE}, {0x2CEF, 0x2CF1},
+ {0x2CF2, 0x2CF3}, {0x2CF9, 0x2CFC}, {0x2CFD, 0x2CFD},
+ {0x2CFE, 0x2CFF}, {0x2D00, 0x2D25}, {0x2D27, 0x2D27},
+ {0x2D2D, 0x2D2D}, {0x2D30, 0x2D67}, {0x2D6F, 0x2D6F},
+ {0x2D70, 0x2D70}, {0x2D7F, 0x2D7F}, {0x2D80, 0x2D96},
+ {0x2DA0, 0x2DA6}, {0x2DA8, 0x2DAE}, {0x2DB0, 0x2DB6},
+ {0x2DB8, 0x2DBE}, {0x2DC0, 0x2DC6}, {0x2DC8, 0x2DCE},
+ {0x2DD0, 0x2DD6}, {0x2DD8, 0x2DDE}, {0x2DE0, 0x2DFF},
+ {0x2E00, 0x2E01}, {0x2E02, 0x2E02}, {0x2E03, 0x2E03},
+ {0x2E04, 0x2E04}, {0x2E05, 0x2E05}, {0x2E06, 0x2E08},
+ {0x2E09, 0x2E09}, {0x2E0A, 0x2E0A}, {0x2E0B, 0x2E0B},
+ {0x2E0C, 0x2E0C}, {0x2E0D, 0x2E0D}, {0x2E0E, 0x2E16},
+ {0x2E17, 0x2E17}, {0x2E18, 0x2E19}, {0x2E1A, 0x2E1A},
+ {0x2E1B, 0x2E1B}, {0x2E1C, 0x2E1C}, {0x2E1D, 0x2E1D},
+ {0x2E1E, 0x2E1F}, {0x2E20, 0x2E20}, {0x2E21, 0x2E21},
+ {0x2E22, 0x2E22}, {0x2E23, 0x2E23}, {0x2E24, 0x2E24},
+ {0x2E25, 0x2E25}, {0x2E26, 0x2E26}, {0x2E27, 0x2E27},
+ {0x2E28, 0x2E28}, {0x2E29, 0x2E29}, {0x2E2A, 0x2E2E},
+ {0x2E2F, 0x2E2F}, {0x2E30, 0x2E39}, {0x2E3A, 0x2E3B},
+ {0x2E3C, 0x2E3F}, {0x2E40, 0x2E40}, {0x2E41, 0x2E41},
+ {0x2E42, 0x2E42}, {0x2E43, 0x2E44}, {0x303F, 0x303F},
+ {0x4DC0, 0x4DFF}, {0xA4D0, 0xA4F7}, {0xA4F8, 0xA4FD},
+ {0xA4FE, 0xA4FF}, {0xA500, 0xA60B}, {0xA60C, 0xA60C},
+ {0xA60D, 0xA60F}, {0xA610, 0xA61F}, {0xA620, 0xA629},
+ {0xA62A, 0xA62B}, {0xA640, 0xA66D}, {0xA66E, 0xA66E},
+ {0xA66F, 0xA66F}, {0xA670, 0xA672}, {0xA673, 0xA673},
+ {0xA674, 0xA67D}, {0xA67E, 0xA67E}, {0xA67F, 0xA67F},
+ {0xA680, 0xA69B}, {0xA69C, 0xA69D}, {0xA69E, 0xA69F},
+ {0xA6A0, 0xA6E5}, {0xA6E6, 0xA6EF}, {0xA6F0, 0xA6F1},
+ {0xA6F2, 0xA6F7}, {0xA700, 0xA716}, {0xA717, 0xA71F},
+ {0xA720, 0xA721}, {0xA722, 0xA76F}, {0xA770, 0xA770},
+ {0xA771, 0xA787}, {0xA788, 0xA788}, {0xA789, 0xA78A},
+ {0xA78B, 0xA78E}, {0xA78F, 0xA78F}, {0xA790, 0xA7AE},
+ {0xA7B0, 0xA7B7}, {0xA7F7, 0xA7F7}, {0xA7F8, 0xA7F9},
+ {0xA7FA, 0xA7FA}, {0xA7FB, 0xA7FF}, {0xA800, 0xA801},
+ {0xA802, 0xA802}, {0xA803, 0xA805}, {0xA806, 0xA806},
+ {0xA807, 0xA80A}, {0xA80B, 0xA80B}, {0xA80C, 0xA822},
+ {0xA823, 0xA824}, {0xA825, 0xA826}, {0xA827, 0xA827},
+ {0xA828, 0xA82B}, {0xA830, 0xA835}, {0xA836, 0xA837},
+ {0xA838, 0xA838}, {0xA839, 0xA839}, {0xA840, 0xA873},
+ {0xA874, 0xA877}, {0xA880, 0xA881}, {0xA882, 0xA8B3},
+ {0xA8B4, 0xA8C3}, {0xA8C4, 0xA8C5}, {0xA8CE, 0xA8CF},
+ {0xA8D0, 0xA8D9}, {0xA8E0, 0xA8F1}, {0xA8F2, 0xA8F7},
+ {0xA8F8, 0xA8FA}, {0xA8FB, 0xA8FB}, {0xA8FC, 0xA8FC},
+ {0xA8FD, 0xA8FD}, {0xA900, 0xA909}, {0xA90A, 0xA925},
+ {0xA926, 0xA92D}, {0xA92E, 0xA92F}, {0xA930, 0xA946},
+ {0xA947, 0xA951}, {0xA952, 0xA953}, {0xA95F, 0xA95F},
+ {0xA980, 0xA982}, {0xA983, 0xA983}, {0xA984, 0xA9B2},
+ {0xA9B3, 0xA9B3}, {0xA9B4, 0xA9B5}, {0xA9B6, 0xA9B9},
+ {0xA9BA, 0xA9BB}, {0xA9BC, 0xA9BC}, {0xA9BD, 0xA9C0},
+ {0xA9C1, 0xA9CD}, {0xA9CF, 0xA9CF}, {0xA9D0, 0xA9D9},
+ {0xA9DE, 0xA9DF}, {0xA9E0, 0xA9E4}, {0xA9E5, 0xA9E5},
+ {0xA9E6, 0xA9E6}, {0xA9E7, 0xA9EF}, {0xA9F0, 0xA9F9},
+ {0xA9FA, 0xA9FE}, {0xAA00, 0xAA28}, {0xAA29, 0xAA2E},
+ {0xAA2F, 0xAA30}, {0xAA31, 0xAA32}, {0xAA33, 0xAA34},
+ {0xAA35, 0xAA36}, {0xAA40, 0xAA42}, {0xAA43, 0xAA43},
+ {0xAA44, 0xAA4B}, {0xAA4C, 0xAA4C}, {0xAA4D, 0xAA4D},
+ {0xAA50, 0xAA59}, {0xAA5C, 0xAA5F}, {0xAA60, 0xAA6F},
+ {0xAA70, 0xAA70}, {0xAA71, 0xAA76}, {0xAA77, 0xAA79},
+ {0xAA7A, 0xAA7A}, {0xAA7B, 0xAA7B}, {0xAA7C, 0xAA7C},
+ {0xAA7D, 0xAA7D}, {0xAA7E, 0xAA7F}, {0xAA80, 0xAAAF},
+ {0xAAB0, 0xAAB0}, {0xAAB1, 0xAAB1}, {0xAAB2, 0xAAB4},
+ {0xAAB5, 0xAAB6}, {0xAAB7, 0xAAB8}, {0xAAB9, 0xAABD},
+ {0xAABE, 0xAABF}, {0xAAC0, 0xAAC0}, {0xAAC1, 0xAAC1},
+ {0xAAC2, 0xAAC2}, {0xAADB, 0xAADC}, {0xAADD, 0xAADD},
+ {0xAADE, 0xAADF}, {0xAAE0, 0xAAEA}, {0xAAEB, 0xAAEB},
+ {0xAAEC, 0xAAED}, {0xAAEE, 0xAAEF}, {0xAAF0, 0xAAF1},
+ {0xAAF2, 0xAAF2}, {0xAAF3, 0xAAF4}, {0xAAF5, 0xAAF5},
+ {0xAAF6, 0xAAF6}, {0xAB01, 0xAB06}, {0xAB09, 0xAB0E},
+ {0xAB11, 0xAB16}, {0xAB20, 0xAB26}, {0xAB28, 0xAB2E},
+ {0xAB30, 0xAB5A}, {0xAB5B, 0xAB5B}, {0xAB5C, 0xAB5F},
+ {0xAB60, 0xAB65}, {0xAB70, 0xABBF}, {0xABC0, 0xABE2},
+ {0xABE3, 0xABE4}, {0xABE5, 0xABE5}, {0xABE6, 0xABE7},
+ {0xABE8, 0xABE8}, {0xABE9, 0xABEA}, {0xABEB, 0xABEB},
+ {0xABEC, 0xABEC}, {0xABED, 0xABED}, {0xABF0, 0xABF9},
+ {0xD7B0, 0xD7C6}, {0xD7CB, 0xD7FB}, {0xD800, 0xDB7F},
+ {0xDB80, 0xDBFF}, {0xDC00, 0xDFFF}, {0xFB00, 0xFB06},
+ {0xFB13, 0xFB17}, {0xFB1D, 0xFB1D}, {0xFB1E, 0xFB1E},
+ {0xFB1F, 0xFB28}, {0xFB29, 0xFB29}, {0xFB2A, 0xFB36},
+ {0xFB38, 0xFB3C}, {0xFB3E, 0xFB3E}, {0xFB40, 0xFB41},
+ {0xFB43, 0xFB44}, {0xFB46, 0xFB4F}, {0xFB50, 0xFBB1},
+ {0xFBB2, 0xFBC1}, {0xFBD3, 0xFD3D}, {0xFD3E, 0xFD3E},
+ {0xFD3F, 0xFD3F}, {0xFD50, 0xFD8F}, {0xFD92, 0xFDC7},
+ {0xFDF0, 0xFDFB}, {0xFDFC, 0xFDFC}, {0xFDFD, 0xFDFD},
+ {0xFE20, 0xFE2F}, {0xFE70, 0xFE74}, {0xFE76, 0xFEFC},
+ {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFC, 0xFFFC},
+ {0x10000, 0x1000B}, {0x1000D, 0x10026}, {0x10028, 0x1003A},
+ {0x1003C, 0x1003D}, {0x1003F, 0x1004D}, {0x10050, 0x1005D},
+ {0x10080, 0x100FA}, {0x10100, 0x10102}, {0x10107, 0x10133},
+ {0x10137, 0x1013F}, {0x10140, 0x10174}, {0x10175, 0x10178},
+ {0x10179, 0x10189}, {0x1018A, 0x1018B}, {0x1018C, 0x1018E},
+ {0x10190, 0x1019B}, {0x101A0, 0x101A0}, {0x101D0, 0x101FC},
+ {0x101FD, 0x101FD}, {0x10280, 0x1029C}, {0x102A0, 0x102D0},
+ {0x102E0, 0x102E0}, {0x102E1, 0x102FB}, {0x10300, 0x1031F},
+ {0x10320, 0x10323}, {0x10330, 0x10340}, {0x10341, 0x10341},
+ {0x10342, 0x10349}, {0x1034A, 0x1034A}, {0x10350, 0x10375},
+ {0x10376, 0x1037A}, {0x10380, 0x1039D}, {0x1039F, 0x1039F},
+ {0x103A0, 0x103C3}, {0x103C8, 0x103CF}, {0x103D0, 0x103D0},
+ {0x103D1, 0x103D5}, {0x10400, 0x1044F}, {0x10450, 0x1047F},
+ {0x10480, 0x1049D}, {0x104A0, 0x104A9}, {0x104B0, 0x104D3},
+ {0x104D8, 0x104FB}, {0x10500, 0x10527}, {0x10530, 0x10563},
+ {0x1056F, 0x1056F}, {0x10600, 0x10736}, {0x10740, 0x10755},
+ {0x10760, 0x10767}, {0x10800, 0x10805}, {0x10808, 0x10808},
+ {0x1080A, 0x10835}, {0x10837, 0x10838}, {0x1083C, 0x1083C},
+ {0x1083F, 0x1083F}, {0x10840, 0x10855}, {0x10857, 0x10857},
+ {0x10858, 0x1085F}, {0x10860, 0x10876}, {0x10877, 0x10878},
+ {0x10879, 0x1087F}, {0x10880, 0x1089E}, {0x108A7, 0x108AF},
+ {0x108E0, 0x108F2}, {0x108F4, 0x108F5}, {0x108FB, 0x108FF},
+ {0x10900, 0x10915}, {0x10916, 0x1091B}, {0x1091F, 0x1091F},
+ {0x10920, 0x10939}, {0x1093F, 0x1093F}, {0x10980, 0x1099F},
+ {0x109A0, 0x109B7}, {0x109BC, 0x109BD}, {0x109BE, 0x109BF},
+ {0x109C0, 0x109CF}, {0x109D2, 0x109FF}, {0x10A00, 0x10A00},
+ {0x10A01, 0x10A03}, {0x10A05, 0x10A06}, {0x10A0C, 0x10A0F},
+ {0x10A10, 0x10A13}, {0x10A15, 0x10A17}, {0x10A19, 0x10A33},
+ {0x10A38, 0x10A3A}, {0x10A3F, 0x10A3F}, {0x10A40, 0x10A47},
+ {0x10A50, 0x10A58}, {0x10A60, 0x10A7C}, {0x10A7D, 0x10A7E},
+ {0x10A7F, 0x10A7F}, {0x10A80, 0x10A9C}, {0x10A9D, 0x10A9F},
+ {0x10AC0, 0x10AC7}, {0x10AC8, 0x10AC8}, {0x10AC9, 0x10AE4},
+ {0x10AE5, 0x10AE6}, {0x10AEB, 0x10AEF}, {0x10AF0, 0x10AF6},
+ {0x10B00, 0x10B35}, {0x10B39, 0x10B3F}, {0x10B40, 0x10B55},
+ {0x10B58, 0x10B5F}, {0x10B60, 0x10B72}, {0x10B78, 0x10B7F},
+ {0x10B80, 0x10B91}, {0x10B99, 0x10B9C}, {0x10BA9, 0x10BAF},
+ {0x10C00, 0x10C48}, {0x10C80, 0x10CB2}, {0x10CC0, 0x10CF2},
+ {0x10CFA, 0x10CFF}, {0x10E60, 0x10E7E}, {0x11000, 0x11000},
+ {0x11001, 0x11001}, {0x11002, 0x11002}, {0x11003, 0x11037},
+ {0x11038, 0x11046}, {0x11047, 0x1104D}, {0x11052, 0x11065},
+ {0x11066, 0x1106F}, {0x1107F, 0x1107F}, {0x11080, 0x11081},
+ {0x11082, 0x11082}, {0x11083, 0x110AF}, {0x110B0, 0x110B2},
+ {0x110B3, 0x110B6}, {0x110B7, 0x110B8}, {0x110B9, 0x110BA},
+ {0x110BB, 0x110BC}, {0x110BD, 0x110BD}, {0x110BE, 0x110C1},
+ {0x110D0, 0x110E8}, {0x110F0, 0x110F9}, {0x11100, 0x11102},
+ {0x11103, 0x11126}, {0x11127, 0x1112B}, {0x1112C, 0x1112C},
+ {0x1112D, 0x11134}, {0x11136, 0x1113F}, {0x11140, 0x11143},
+ {0x11150, 0x11172}, {0x11173, 0x11173}, {0x11174, 0x11175},
+ {0x11176, 0x11176}, {0x11180, 0x11181}, {0x11182, 0x11182},
+ {0x11183, 0x111B2}, {0x111B3, 0x111B5}, {0x111B6, 0x111BE},
+ {0x111BF, 0x111C0}, {0x111C1, 0x111C4}, {0x111C5, 0x111C9},
+ {0x111CA, 0x111CC}, {0x111CD, 0x111CD}, {0x111D0, 0x111D9},
+ {0x111DA, 0x111DA}, {0x111DB, 0x111DB}, {0x111DC, 0x111DC},
+ {0x111DD, 0x111DF}, {0x111E1, 0x111F4}, {0x11200, 0x11211},
+ {0x11213, 0x1122B}, {0x1122C, 0x1122E}, {0x1122F, 0x11231},
+ {0x11232, 0x11233}, {0x11234, 0x11234}, {0x11235, 0x11235},
+ {0x11236, 0x11237}, {0x11238, 0x1123D}, {0x1123E, 0x1123E},
+ {0x11280, 0x11286}, {0x11288, 0x11288}, {0x1128A, 0x1128D},
+ {0x1128F, 0x1129D}, {0x1129F, 0x112A8}, {0x112A9, 0x112A9},
+ {0x112B0, 0x112DE}, {0x112DF, 0x112DF}, {0x112E0, 0x112E2},
+ {0x112E3, 0x112EA}, {0x112F0, 0x112F9}, {0x11300, 0x11301},
+ {0x11302, 0x11303}, {0x11305, 0x1130C}, {0x1130F, 0x11310},
+ {0x11313, 0x11328}, {0x1132A, 0x11330}, {0x11332, 0x11333},
+ {0x11335, 0x11339}, {0x1133C, 0x1133C}, {0x1133D, 0x1133D},
+ {0x1133E, 0x1133F}, {0x11340, 0x11340}, {0x11341, 0x11344},
+ {0x11347, 0x11348}, {0x1134B, 0x1134D}, {0x11350, 0x11350},
+ {0x11357, 0x11357}, {0x1135D, 0x11361}, {0x11362, 0x11363},
+ {0x11366, 0x1136C}, {0x11370, 0x11374}, {0x11400, 0x11434},
+ {0x11435, 0x11437}, {0x11438, 0x1143F}, {0x11440, 0x11441},
+ {0x11442, 0x11444}, {0x11445, 0x11445}, {0x11446, 0x11446},
+ {0x11447, 0x1144A}, {0x1144B, 0x1144F}, {0x11450, 0x11459},
+ {0x1145B, 0x1145B}, {0x1145D, 0x1145D}, {0x11480, 0x114AF},
+ {0x114B0, 0x114B2}, {0x114B3, 0x114B8}, {0x114B9, 0x114B9},
+ {0x114BA, 0x114BA}, {0x114BB, 0x114BE}, {0x114BF, 0x114C0},
+ {0x114C1, 0x114C1}, {0x114C2, 0x114C3}, {0x114C4, 0x114C5},
+ {0x114C6, 0x114C6}, {0x114C7, 0x114C7}, {0x114D0, 0x114D9},
+ {0x11580, 0x115AE}, {0x115AF, 0x115B1}, {0x115B2, 0x115B5},
+ {0x115B8, 0x115BB}, {0x115BC, 0x115BD}, {0x115BE, 0x115BE},
+ {0x115BF, 0x115C0}, {0x115C1, 0x115D7}, {0x115D8, 0x115DB},
+ {0x115DC, 0x115DD}, {0x11600, 0x1162F}, {0x11630, 0x11632},
+ {0x11633, 0x1163A}, {0x1163B, 0x1163C}, {0x1163D, 0x1163D},
+ {0x1163E, 0x1163E}, {0x1163F, 0x11640}, {0x11641, 0x11643},
+ {0x11644, 0x11644}, {0x11650, 0x11659}, {0x11660, 0x1166C},
+ {0x11680, 0x116AA}, {0x116AB, 0x116AB}, {0x116AC, 0x116AC},
+ {0x116AD, 0x116AD}, {0x116AE, 0x116AF}, {0x116B0, 0x116B5},
+ {0x116B6, 0x116B6}, {0x116B7, 0x116B7}, {0x116C0, 0x116C9},
+ {0x11700, 0x11719}, {0x1171D, 0x1171F}, {0x11720, 0x11721},
+ {0x11722, 0x11725}, {0x11726, 0x11726}, {0x11727, 0x1172B},
+ {0x11730, 0x11739}, {0x1173A, 0x1173B}, {0x1173C, 0x1173E},
+ {0x1173F, 0x1173F}, {0x118A0, 0x118DF}, {0x118E0, 0x118E9},
+ {0x118EA, 0x118F2}, {0x118FF, 0x118FF}, {0x11AC0, 0x11AF8},
+ {0x11C00, 0x11C08}, {0x11C0A, 0x11C2E}, {0x11C2F, 0x11C2F},
+ {0x11C30, 0x11C36}, {0x11C38, 0x11C3D}, {0x11C3E, 0x11C3E},
+ {0x11C3F, 0x11C3F}, {0x11C40, 0x11C40}, {0x11C41, 0x11C45},
+ {0x11C50, 0x11C59}, {0x11C5A, 0x11C6C}, {0x11C70, 0x11C71},
+ {0x11C72, 0x11C8F}, {0x11C92, 0x11CA7}, {0x11CA9, 0x11CA9},
+ {0x11CAA, 0x11CB0}, {0x11CB1, 0x11CB1}, {0x11CB2, 0x11CB3},
+ {0x11CB4, 0x11CB4}, {0x11CB5, 0x11CB6}, {0x12000, 0x12399},
+ {0x12400, 0x1246E}, {0x12470, 0x12474}, {0x12480, 0x12543},
+ {0x13000, 0x1342E}, {0x14400, 0x14646}, {0x16800, 0x16A38},
+ {0x16A40, 0x16A5E}, {0x16A60, 0x16A69}, {0x16A6E, 0x16A6F},
+ {0x16AD0, 0x16AED}, {0x16AF0, 0x16AF4}, {0x16AF5, 0x16AF5},
+ {0x16B00, 0x16B2F}, {0x16B30, 0x16B36}, {0x16B37, 0x16B3B},
+ {0x16B3C, 0x16B3F}, {0x16B40, 0x16B43}, {0x16B44, 0x16B44},
+ {0x16B45, 0x16B45}, {0x16B50, 0x16B59}, {0x16B5B, 0x16B61},
+ {0x16B63, 0x16B77}, {0x16B7D, 0x16B8F}, {0x16F00, 0x16F44},
+ {0x16F50, 0x16F50}, {0x16F51, 0x16F7E}, {0x16F8F, 0x16F92},
+ {0x16F93, 0x16F9F}, {0x1BC00, 0x1BC6A}, {0x1BC70, 0x1BC7C},
+ {0x1BC80, 0x1BC88}, {0x1BC90, 0x1BC99}, {0x1BC9C, 0x1BC9C},
+ {0x1BC9D, 0x1BC9E}, {0x1BC9F, 0x1BC9F}, {0x1BCA0, 0x1BCA3},
+ {0x1D000, 0x1D0F5}, {0x1D100, 0x1D126}, {0x1D129, 0x1D164},
+ {0x1D165, 0x1D166}, {0x1D167, 0x1D169}, {0x1D16A, 0x1D16C},
+ {0x1D16D, 0x1D172}, {0x1D173, 0x1D17A}, {0x1D17B, 0x1D182},
+ {0x1D183, 0x1D184}, {0x1D185, 0x1D18B}, {0x1D18C, 0x1D1A9},
+ {0x1D1AA, 0x1D1AD}, {0x1D1AE, 0x1D1E8}, {0x1D200, 0x1D241},
+ {0x1D242, 0x1D244}, {0x1D245, 0x1D245}, {0x1D300, 0x1D356},
+ {0x1D360, 0x1D371}, {0x1D400, 0x1D454}, {0x1D456, 0x1D49C},
+ {0x1D49E, 0x1D49F}, {0x1D4A2, 0x1D4A2}, {0x1D4A5, 0x1D4A6},
+ {0x1D4A9, 0x1D4AC}, {0x1D4AE, 0x1D4B9}, {0x1D4BB, 0x1D4BB},
+ {0x1D4BD, 0x1D4C3}, {0x1D4C5, 0x1D505}, {0x1D507, 0x1D50A},
+ {0x1D50D, 0x1D514}, {0x1D516, 0x1D51C}, {0x1D51E, 0x1D539},
+ {0x1D53B, 0x1D53E}, {0x1D540, 0x1D544}, {0x1D546, 0x1D546},
+ {0x1D54A, 0x1D550}, {0x1D552, 0x1D6A5}, {0x1D6A8, 0x1D6C0},
+ {0x1D6C1, 0x1D6C1}, {0x1D6C2, 0x1D6DA}, {0x1D6DB, 0x1D6DB},
+ {0x1D6DC, 0x1D6FA}, {0x1D6FB, 0x1D6FB}, {0x1D6FC, 0x1D714},
+ {0x1D715, 0x1D715}, {0x1D716, 0x1D734}, {0x1D735, 0x1D735},
+ {0x1D736, 0x1D74E}, {0x1D74F, 0x1D74F}, {0x1D750, 0x1D76E},
+ {0x1D76F, 0x1D76F}, {0x1D770, 0x1D788}, {0x1D789, 0x1D789},
+ {0x1D78A, 0x1D7A8}, {0x1D7A9, 0x1D7A9}, {0x1D7AA, 0x1D7C2},
+ {0x1D7C3, 0x1D7C3}, {0x1D7C4, 0x1D7CB}, {0x1D7CE, 0x1D7FF},
+ {0x1D800, 0x1D9FF}, {0x1DA00, 0x1DA36}, {0x1DA37, 0x1DA3A},
+ {0x1DA3B, 0x1DA6C}, {0x1DA6D, 0x1DA74}, {0x1DA75, 0x1DA75},
+ {0x1DA76, 0x1DA83}, {0x1DA84, 0x1DA84}, {0x1DA85, 0x1DA86},
+ {0x1DA87, 0x1DA8B}, {0x1DA9B, 0x1DA9F}, {0x1DAA1, 0x1DAAF},
+ {0x1E000, 0x1E006}, {0x1E008, 0x1E018}, {0x1E01B, 0x1E021},
+ {0x1E023, 0x1E024}, {0x1E026, 0x1E02A}, {0x1E800, 0x1E8C4},
+ {0x1E8C7, 0x1E8CF}, {0x1E8D0, 0x1E8D6}, {0x1E900, 0x1E943},
+ {0x1E944, 0x1E94A}, {0x1E950, 0x1E959}, {0x1E95E, 0x1E95F},
+ {0x1EE00, 0x1EE03}, {0x1EE05, 0x1EE1F}, {0x1EE21, 0x1EE22},
+ {0x1EE24, 0x1EE24}, {0x1EE27, 0x1EE27}, {0x1EE29, 0x1EE32},
+ {0x1EE34, 0x1EE37}, {0x1EE39, 0x1EE39}, {0x1EE3B, 0x1EE3B},
+ {0x1EE42, 0x1EE42}, {0x1EE47, 0x1EE47}, {0x1EE49, 0x1EE49},
+ {0x1EE4B, 0x1EE4B}, {0x1EE4D, 0x1EE4F}, {0x1EE51, 0x1EE52},
+ {0x1EE54, 0x1EE54}, {0x1EE57, 0x1EE57}, {0x1EE59, 0x1EE59},
+ {0x1EE5B, 0x1EE5B}, {0x1EE5D, 0x1EE5D}, {0x1EE5F, 0x1EE5F},
+ {0x1EE61, 0x1EE62}, {0x1EE64, 0x1EE64}, {0x1EE67, 0x1EE6A},
+ {0x1EE6C, 0x1EE72}, {0x1EE74, 0x1EE77}, {0x1EE79, 0x1EE7C},
+ {0x1EE7E, 0x1EE7E}, {0x1EE80, 0x1EE89}, {0x1EE8B, 0x1EE9B},
+ {0x1EEA1, 0x1EEA3}, {0x1EEA5, 0x1EEA9}, {0x1EEAB, 0x1EEBB},
+ {0x1EEF0, 0x1EEF1}, {0x1F000, 0x1F003}, {0x1F005, 0x1F02B},
+ {0x1F030, 0x1F093}, {0x1F0A0, 0x1F0AE}, {0x1F0B1, 0x1F0BF},
+ {0x1F0C1, 0x1F0CE}, {0x1F0D1, 0x1F0F5}, {0x1F10B, 0x1F10C},
+ {0x1F12E, 0x1F12E}, {0x1F16A, 0x1F16B}, {0x1F1E6, 0x1F1FF},
+ {0x1F321, 0x1F32C}, {0x1F336, 0x1F336}, {0x1F37D, 0x1F37D},
+ {0x1F394, 0x1F39F}, {0x1F3CB, 0x1F3CE}, {0x1F3D4, 0x1F3DF},
+ {0x1F3F1, 0x1F3F3}, {0x1F3F5, 0x1F3F7}, {0x1F43F, 0x1F43F},
+ {0x1F441, 0x1F441}, {0x1F4FD, 0x1F4FE}, {0x1F53E, 0x1F54A},
+ {0x1F54F, 0x1F54F}, {0x1F568, 0x1F579}, {0x1F57B, 0x1F594},
+ {0x1F597, 0x1F5A3}, {0x1F5A5, 0x1F5FA}, {0x1F650, 0x1F67F},
+ {0x1F6C6, 0x1F6CB}, {0x1F6CD, 0x1F6CF}, {0x1F6E0, 0x1F6EA},
+ {0x1F6F0, 0x1F6F3}, {0x1F700, 0x1F773}, {0x1F780, 0x1F7D4},
+ {0x1F800, 0x1F80B}, {0x1F810, 0x1F847}, {0x1F850, 0x1F859},
+ {0x1F860, 0x1F887}, {0x1F890, 0x1F8AD}, {0xE0001, 0xE0001},
+ {0xE0020, 0xE007F},
+}
+
+// Condition have flag EastAsianWidth whether the current locale is CJK or not.
+type Condition struct {
+ EastAsianWidth bool
+}
+
+// NewCondition return new instance of Condition which is current locale.
+func NewCondition() *Condition {
+ return &Condition{EastAsianWidth}
+}
+
+// RuneWidth returns the number of cells in r.
+// See http://www.unicode.org/reports/tr11/
+func (c *Condition) RuneWidth(r rune) int {
+ switch {
+ case r < 0 || r > 0x10FFFF ||
+ inTables(r, nonprint, combining, notassigned):
+ return 0
+ case (c.EastAsianWidth && IsAmbiguousWidth(r)) ||
+ inTables(r, doublewidth, emoji):
+ return 2
+ default:
+ return 1
+ }
+}
+
+// StringWidth return width as you can see
+func (c *Condition) StringWidth(s string) (width int) {
+ for _, r := range []rune(s) {
+ width += c.RuneWidth(r)
+ }
+ return width
+}
+
+// Truncate return string truncated with w cells
+func (c *Condition) Truncate(s string, w int, tail string) string {
+ if c.StringWidth(s) <= w {
+ return s
+ }
+ r := []rune(s)
+ tw := c.StringWidth(tail)
+ w -= tw
+ width := 0
+ i := 0
+ for ; i < len(r); i++ {
+ cw := c.RuneWidth(r[i])
+ if width+cw > w {
+ break
+ }
+ width += cw
+ }
+ return string(r[0:i]) + tail
+}
+
+// Wrap return string wrapped with w cells
+func (c *Condition) Wrap(s string, w int) string {
+ width := 0
+ out := ""
+ for _, r := range []rune(s) {
+ cw := RuneWidth(r)
+ if r == '\n' {
+ out += string(r)
+ width = 0
+ continue
+ } else if width+cw > w {
+ out += "\n"
+ width = 0
+ out += string(r)
+ width += cw
+ continue
+ }
+ out += string(r)
+ width += cw
+ }
+ return out
+}
+
+// FillLeft return string filled in left by spaces in w cells
+func (c *Condition) FillLeft(s string, w int) string {
+ width := c.StringWidth(s)
+ count := w - width
+ if count > 0 {
+ b := make([]byte, count)
+ for i := range b {
+ b[i] = ' '
+ }
+ return string(b) + s
+ }
+ return s
+}
+
+// FillRight return string filled in left by spaces in w cells
+func (c *Condition) FillRight(s string, w int) string {
+ width := c.StringWidth(s)
+ count := w - width
+ if count > 0 {
+ b := make([]byte, count)
+ for i := range b {
+ b[i] = ' '
+ }
+ return s + string(b)
+ }
+ return s
+}
+
+// RuneWidth returns the number of cells in r.
+// See http://www.unicode.org/reports/tr11/
+func RuneWidth(r rune) int {
+ return DefaultCondition.RuneWidth(r)
+}
+
+// IsAmbiguousWidth returns whether is ambiguous width or not.
+func IsAmbiguousWidth(r rune) bool {
+ return inTables(r, private, ambiguous)
+}
+
+// IsNeutralWidth returns whether is neutral width or not.
+func IsNeutralWidth(r rune) bool {
+ return inTable(r, neutral)
+}
+
+// StringWidth return width as you can see
+func StringWidth(s string) (width int) {
+ return DefaultCondition.StringWidth(s)
+}
+
+// Truncate return string truncated with w cells
+func Truncate(s string, w int, tail string) string {
+ return DefaultCondition.Truncate(s, w, tail)
+}
+
+// Wrap return string wrapped with w cells
+func Wrap(s string, w int) string {
+ return DefaultCondition.Wrap(s, w)
+}
+
+// FillLeft return string filled in left by spaces in w cells
+func FillLeft(s string, w int) string {
+ return DefaultCondition.FillLeft(s, w)
+}
+
+// FillRight return string filled in left by spaces in w cells
+func FillRight(s string, w int) string {
+ return DefaultCondition.FillRight(s, w)
+}
diff --git a/vendor/github.com/mattn/go-runewidth/runewidth_js.go b/vendor/github.com/mattn/go-runewidth/runewidth_js.go
new file mode 100644
index 0000000..0ce32c5
--- /dev/null
+++ b/vendor/github.com/mattn/go-runewidth/runewidth_js.go
@@ -0,0 +1,8 @@
+// +build js
+
+package runewidth
+
+func IsEastAsian() bool {
+ // TODO: Implement this for the web. Detect east asian in a compatible way, and return true.
+ return false
+}
diff --git a/vendor/github.com/mattn/go-runewidth/runewidth_posix.go b/vendor/github.com/mattn/go-runewidth/runewidth_posix.go
new file mode 100644
index 0000000..c579e9a
--- /dev/null
+++ b/vendor/github.com/mattn/go-runewidth/runewidth_posix.go
@@ -0,0 +1,77 @@
+// +build !windows,!js
+
+package runewidth
+
+import (
+ "os"
+ "regexp"
+ "strings"
+)
+
+var reLoc = regexp.MustCompile(`^[a-z][a-z][a-z]?(?:_[A-Z][A-Z])?\.(.+)`)
+
+var mblenTable = map[string]int{
+ "utf-8": 6,
+ "utf8": 6,
+ "jis": 8,
+ "eucjp": 3,
+ "euckr": 2,
+ "euccn": 2,
+ "sjis": 2,
+ "cp932": 2,
+ "cp51932": 2,
+ "cp936": 2,
+ "cp949": 2,
+ "cp950": 2,
+ "big5": 2,
+ "gbk": 2,
+ "gb2312": 2,
+}
+
+func isEastAsian(locale string) bool {
+ charset := strings.ToLower(locale)
+ r := reLoc.FindStringSubmatch(locale)
+ if len(r) == 2 {
+ charset = strings.ToLower(r[1])
+ }
+
+ if strings.HasSuffix(charset, "@cjk_narrow") {
+ return false
+ }
+
+ for pos, b := range []byte(charset) {
+ if b == '@' {
+ charset = charset[:pos]
+ break
+ }
+ }
+ max := 1
+ if m, ok := mblenTable[charset]; ok {
+ max = m
+ }
+ if max > 1 && (charset[0] != 'u' ||
+ strings.HasPrefix(locale, "ja") ||
+ strings.HasPrefix(locale, "ko") ||
+ strings.HasPrefix(locale, "zh")) {
+ return true
+ }
+ return false
+}
+
+// IsEastAsian return true if the current locale is CJK
+func IsEastAsian() bool {
+ locale := os.Getenv("LC_CTYPE")
+ if locale == "" {
+ locale = os.Getenv("LANG")
+ }
+
+ // ignore C locale
+ if locale == "POSIX" || locale == "C" {
+ return false
+ }
+ if len(locale) > 1 && locale[0] == 'C' && (locale[1] == '.' || locale[1] == '-') {
+ return false
+ }
+
+ return isEastAsian(locale)
+}
diff --git a/vendor/github.com/mattn/go-runewidth/runewidth_test.go b/vendor/github.com/mattn/go-runewidth/runewidth_test.go
new file mode 100644
index 0000000..b0378a1
--- /dev/null
+++ b/vendor/github.com/mattn/go-runewidth/runewidth_test.go
@@ -0,0 +1,275 @@
+package runewidth
+
+import (
+ "sort"
+ "testing"
+)
+
+var _ sort.Interface = (*table)(nil)
+
+func (t table) Len() int {
+ return len(t)
+}
+
+func (t table) Less(i, j int) bool {
+ return t[i].first < t[j].first
+}
+
+func (t *table) Swap(i, j int) {
+ (*t)[i], (*t)[j] = (*t)[j], (*t)[i]
+}
+
+var tables = []table{
+ private,
+ nonprint,
+ combining,
+ doublewidth,
+ ambiguous,
+ emoji,
+ notassigned,
+ neutral,
+}
+
+func TestSorted(t *testing.T) {
+ for _, tbl := range tables {
+ if !sort.IsSorted(&tbl) {
+ t.Errorf("not sorted")
+ }
+ }
+}
+
+var runewidthtests = []struct {
+ in rune
+ out int
+ eaout int
+}{
+ {'世', 2, 2},
+ {'界', 2, 2},
+ {'セ', 1, 1},
+ {'カ', 1, 1},
+ {'イ', 1, 1},
+ {'☆', 1, 2}, // double width in ambiguous
+ {'\x00', 0, 0},
+ {'\x01', 0, 0},
+ {'\u0300', 0, 0},
+}
+
+func TestRuneWidth(t *testing.T) {
+ c := NewCondition()
+ for _, tt := range runewidthtests {
+ if out := c.RuneWidth(tt.in); out != tt.out {
+ t.Errorf("RuneWidth(%q) = %d, want %d", tt.in, out, tt.out)
+ }
+ }
+ c.EastAsianWidth = true
+ for _, tt := range runewidthtests {
+ if out := c.RuneWidth(tt.in); out != tt.eaout {
+ t.Errorf("RuneWidth(%q) = %d, want %d", tt.in, out, tt.eaout)
+ }
+ }
+}
+
+var isambiguouswidthtests = []struct {
+ in rune
+ out bool
+}{
+ {'世', false},
+ {'■', true},
+ {'界', false},
+ {'○', true},
+ {'㈱', false},
+ {'①', true},
+ {'②', true},
+ {'③', true},
+ {'④', true},
+ {'⑤', true},
+ {'⑥', true},
+ {'⑦', true},
+ {'⑧', true},
+ {'⑨', true},
+ {'⑩', true},
+ {'⑪', true},
+ {'⑫', true},
+ {'⑬', true},
+ {'⑭', true},
+ {'⑮', true},
+ {'⑯', true},
+ {'⑰', true},
+ {'⑱', true},
+ {'⑲', true},
+ {'⑳', true},
+ {'☆', true},
+}
+
+func TestIsAmbiguousWidth(t *testing.T) {
+ for _, tt := range isambiguouswidthtests {
+ if out := IsAmbiguousWidth(tt.in); out != tt.out {
+ t.Errorf("IsAmbiguousWidth(%q) = %v, want %v", tt.in, out, tt.out)
+ }
+ }
+}
+
+var stringwidthtests = []struct {
+ in string
+ out int
+ eaout int
+}{
+ {"■㈱の世界①", 10, 12},
+ {"スター☆", 7, 8},
+ {"つのだ☆HIRO", 11, 12},
+}
+
+func TestStringWidth(t *testing.T) {
+ c := NewCondition()
+ for _, tt := range stringwidthtests {
+ if out := c.StringWidth(tt.in); out != tt.out {
+ t.Errorf("StringWidth(%q) = %q, want %q", tt.in, out, tt.out)
+ }
+ }
+ c.EastAsianWidth = true
+ for _, tt := range stringwidthtests {
+ if out := c.StringWidth(tt.in); out != tt.eaout {
+ t.Errorf("StringWidth(%q) = %q, want %q", tt.in, out, tt.eaout)
+ }
+ }
+}
+
+func TestStringWidthInvalid(t *testing.T) {
+ s := "こんにちわ\x00世界"
+ if out := StringWidth(s); out != 14 {
+ t.Errorf("StringWidth(%q) = %q, want %q", s, out, 14)
+ }
+}
+
+func TestTruncateSmaller(t *testing.T) {
+ s := "あいうえお"
+ expected := "あいうえお"
+
+ if out := Truncate(s, 10, "..."); out != expected {
+ t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
+ }
+}
+
+func TestTruncate(t *testing.T) {
+ s := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
+ expected := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおお..."
+ out := Truncate(s, 80, "...")
+ if out != expected {
+ t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
+ }
+ width := StringWidth(out)
+ if width != 79 {
+ t.Errorf("width of Truncate(%q) should be %d, but %d", s, 79, width)
+ }
+}
+
+func TestTruncateFit(t *testing.T) {
+ s := "aあいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
+ expected := "aあいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおお..."
+
+ out := Truncate(s, 80, "...")
+ if out != expected {
+ t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
+ }
+ width := StringWidth(out)
+ if width != 80 {
+ t.Errorf("width of Truncate(%q) should be %d, but %d", s, 80, width)
+ }
+}
+
+func TestTruncateJustFit(t *testing.T) {
+ s := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
+ expected := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
+
+ out := Truncate(s, 80, "...")
+ if out != expected {
+ t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
+ }
+ width := StringWidth(out)
+ if width != 80 {
+ t.Errorf("width of Truncate(%q) should be %d, but %d", s, 80, width)
+ }
+}
+
+func TestWrap(t *testing.T) {
+ s := `東京特許許可局局長はよく柿喰う客だ/東京特許許可局局長はよく柿喰う客だ
+123456789012345678901234567890
+
+END`
+ expected := `東京特許許可局局長はよく柿喰う
+客だ/東京特許許可局局長はよく
+柿喰う客だ
+123456789012345678901234567890
+
+END`
+
+ if out := Wrap(s, 30); out != expected {
+ t.Errorf("Wrap(%q) = %q, want %q", s, out, expected)
+ }
+}
+
+func TestTruncateNoNeeded(t *testing.T) {
+ s := "あいうえおあい"
+ expected := "あいうえおあい"
+
+ if out := Truncate(s, 80, "..."); out != expected {
+ t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
+ }
+}
+
+var isneutralwidthtests = []struct {
+ in rune
+ out bool
+}{
+ {'→', false},
+ {'┊', false},
+ {'┈', false},
+ {'~', false},
+ {'└', false},
+ {'⣀', true},
+ {'⣀', true},
+}
+
+func TestIsNeutralWidth(t *testing.T) {
+ for _, tt := range isneutralwidthtests {
+ if out := IsNeutralWidth(tt.in); out != tt.out {
+ t.Errorf("IsNeutralWidth(%q) = %v, want %v", tt.in, out, tt.out)
+ }
+ }
+}
+
+func TestFillLeft(t *testing.T) {
+ s := "あxいうえお"
+ expected := " あxいうえお"
+
+ if out := FillLeft(s, 15); out != expected {
+ t.Errorf("FillLeft(%q) = %q, want %q", s, out, expected)
+ }
+}
+
+func TestFillLeftFit(t *testing.T) {
+ s := "あいうえお"
+ expected := "あいうえお"
+
+ if out := FillLeft(s, 10); out != expected {
+ t.Errorf("FillLeft(%q) = %q, want %q", s, out, expected)
+ }
+}
+
+func TestFillRight(t *testing.T) {
+ s := "あxいうえお"
+ expected := "あxいうえお "
+
+ if out := FillRight(s, 15); out != expected {
+ t.Errorf("FillRight(%q) = %q, want %q", s, out, expected)
+ }
+}
+
+func TestFillRightFit(t *testing.T) {
+ s := "あいうえお"
+ expected := "あいうえお"
+
+ if out := FillRight(s, 10); out != expected {
+ t.Errorf("FillRight(%q) = %q, want %q", s, out, expected)
+ }
+}
diff --git a/vendor/github.com/mattn/go-runewidth/runewidth_windows.go b/vendor/github.com/mattn/go-runewidth/runewidth_windows.go
new file mode 100644
index 0000000..0258876
--- /dev/null
+++ b/vendor/github.com/mattn/go-runewidth/runewidth_windows.go
@@ -0,0 +1,25 @@
+package runewidth
+
+import (
+ "syscall"
+)
+
+var (
+ kernel32 = syscall.NewLazyDLL("kernel32")
+ procGetConsoleOutputCP = kernel32.NewProc("GetConsoleOutputCP")
+)
+
+// IsEastAsian return true if the current locale is CJK
+func IsEastAsian() bool {
+ r1, _, _ := procGetConsoleOutputCP.Call()
+ if r1 == 0 {
+ return false
+ }
+
+ switch int(r1) {
+ case 932, 51932, 936, 949, 950:
+ return true
+ }
+
+ return false
+}
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/.gitignore b/vendor/github.com/miguelmota/go-coinmarketcap/.gitignore
new file mode 100644
index 0000000..bf2bb1d
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/.gitignore
@@ -0,0 +1,26 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+
+.DS_Store
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/.travis.yml b/vendor/github.com/miguelmota/go-coinmarketcap/.travis.yml
new file mode 100644
index 0000000..1a5f7a1
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/.travis.yml
@@ -0,0 +1,9 @@
+language: go
+
+go:
+ - "1.9.x"
+ - "1.10.x"
+ - "master"
+
+script:
+ - make test
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/Gopkg.lock b/vendor/github.com/miguelmota/go-coinmarketcap/Gopkg.lock
new file mode 100644
index 0000000..b8f3d3b
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/Gopkg.lock
@@ -0,0 +1,24 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+ name = "github.com/anaskhan96/soup"
+ packages = ["."]
+ revision = "7a8d31f81bad1a5abeed0d0219c35e5e295a5a76"
+ version = "v1.0.1"
+
+[[projects]]
+ branch = "master"
+ name = "golang.org/x/net"
+ packages = [
+ "html",
+ "html/atom"
+ ]
+ revision = "dc948dff8834a7fe1ca525f8d04e261c2b56e70d"
+
+[solve-meta]
+ analyzer-name = "dep"
+ analyzer-version = 1
+ inputs-digest = "ef92c4a55490132c4b677cba28dd5f0ee0bb397d0b3581ea46df6c3f4f169646"
+ solver-name = "gps-cdcl"
+ solver-version = 1
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/Gopkg.toml b/vendor/github.com/miguelmota/go-coinmarketcap/Gopkg.toml
new file mode 100644
index 0000000..f0a6218
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/Gopkg.toml
@@ -0,0 +1,34 @@
+# Gopkg.toml example
+#
+# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+# name = "github.com/user/project"
+# version = "1.0.0"
+#
+# [[constraint]]
+# name = "github.com/user/project2"
+# branch = "dev"
+# source = "github.com/myfork/project2"
+#
+# [[override]]
+# name = "github.com/x/y"
+# version = "2.4.0"
+#
+# [prune]
+# non-go = false
+# go-tests = true
+# unused-packages = true
+
+
+[[constraint]]
+ name = "github.com/anaskhan96/soup"
+ version = "1.0.1"
+
+[prune]
+ go-tests = true
+ unused-packages = true
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/LICENSE.md b/vendor/github.com/miguelmota/go-coinmarketcap/LICENSE.md
new file mode 100755
index 0000000..eb0736f
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/LICENSE.md
@@ -0,0 +1,21 @@
+MIT license
+
+Copyright (C) 2015 Miguel Mota
+
+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.
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/Makefile b/vendor/github.com/miguelmota/go-coinmarketcap/Makefile
new file mode 100644
index 0000000..10b2bf7
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/Makefile
@@ -0,0 +1,5 @@
+all:
+ @echo "no default"
+
+test:
+ go test -v
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/README.md b/vendor/github.com/miguelmota/go-coinmarketcap/README.md
new file mode 100755
index 0000000..c55fe49
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/README.md
@@ -0,0 +1,47 @@
+# go-coinmarketcap
+
+> The Unofficial [CoinMarketCap](https://coinmarketcap.com/) API client for Go.
+
+[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/miguelmota/go-coinmarketcap/master/LICENSE.md) [![Build Status](https://travis-ci.org/miguelmota/go-coinmarketcap.svg?branch=master)](https://travis-ci.org/miguelmota/go-coinmarketcap) [![Go Report Card](https://goreportcard.com/badge/github.com/miguelmota/go-coinmarketcap?)](https://goreportcard.com/report/github.com/miguelmota/go-coinmarketcap) [![GoDoc](https://godoc.org/github.com/miguelmota/go-coinmarketcap?status.svg)](https://godoc.org/github.com/miguelmota/go-coinmarketcap)
+
+## Documentation
+
+[https://godoc.org/github.com/miguelmota/go-coinmarketcap](https://godoc.org/github.com/miguelmota/go-coinmarketcap)
+
+## Install
+
+```bash
+go get -u github.com/miguelmota/go-coinmarketcap
+```
+
+## Getting started
+
+```go
+package main
+
+import (
+ "fmt"
+ "log"
+
+ cmc "github.com/miguelmota/go-coinmarketcap"
+)
+
+func main() {
+ coins, err := cmc.GetAllCoinData(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for _, coin := range coins {
+ fmt.Println(coin.Symbol, coin.PriceUSD)
+ }
+}
+```
+
+## Examples
+
+Check out the [`./example`](./example) directory and documentation.
+
+## License
+
+MIT
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/coinmarketcap.go b/vendor/github.com/miguelmota/go-coinmarketcap/coinmarketcap.go
new file mode 100644
index 0000000..899ff3d
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/coinmarketcap.go
@@ -0,0 +1,210 @@
+// Package coinmarketcap Coin Market Cap API client for Go
+package coinmarketcap
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/anaskhan96/soup"
+)
+
+var (
+ baseURL = "https://api.coinmarketcap.com/v1"
+ coinGraphURL = "https://graphs2.coinmarketcap.com/currencies"
+ globalMarketGraphURL = "https://graphs2.coinmarketcap.com/global/marketcap-total"
+ altcoinMarketGraphURL = "https://graphs2.coinmarketcap.com/global/marketcap-altcoin"
+)
+
+// GetGlobalMarketData get information about the global market data of the cryptocurrencies
+func GetGlobalMarketData() (GlobalMarketData, error) {
+ url := fmt.Sprintf(baseURL + "/global/")
+
+ resp, err := makeReq(url)
+
+ var data GlobalMarketData
+ err = json.Unmarshal(resp, &data)
+ if err != nil {
+ return GlobalMarketData{}, err
+ }
+
+ return data, nil
+}
+
+// GetGlobalMarketGraphData get graph data points of global market
+func GetGlobalMarketGraphData(start int64, end int64) (MarketGraph, error) {
+ url := fmt.Sprintf("%s/%d/%d", globalMarketGraphURL, start*1000, end*1000)
+ resp, err := makeReq(url)
+ if err != nil {
+ return MarketGraph{}, err
+ }
+ var data MarketGraph
+ err = json.Unmarshal(resp, &data)
+ if err != nil {
+ return MarketGraph{}, err
+ }
+
+ return data, nil
+}
+
+// GetAltcoinMarketGraphData get graph data points of altcoin market
+func GetAltcoinMarketGraphData(start int64, end int64) (MarketGraph, error) {
+ url := fmt.Sprintf("%s/%d/%d", altcoinMarketGraphURL, start*1000, end*1000)
+ resp, err := makeReq(url)
+ if err != nil {
+ return MarketGraph{}, err
+ }
+ var data MarketGraph
+ err = json.Unmarshal(resp, &data)
+ if err != nil {
+ return MarketGraph{}, err
+ }
+
+ return data, nil
+}
+
+// GetCoinData get information about a crypto currency
+func GetCoinData(coin string) (Coin, error) {
+ coin = strings.ToLower(coin)
+ url := fmt.Sprintf("%s/ticker/%s", baseURL, coin)
+ resp, err := makeReq(url)
+ if err != nil {
+ return Coin{}, err
+ }
+ var data []Coin
+ err = json.Unmarshal(resp, &data)
+ if err != nil {
+ return Coin{}, err
+ }
+
+ return data[0], nil
+}
+
+// GetAllCoinData get information about all coins listed in Coin Market Cap
+func GetAllCoinData(limit int) (map[string]Coin, error) {
+ var l string
+ if limit >= 0 {
+ l = fmt.Sprintf("?limit=%v", limit)
+ }
+ url := fmt.Sprintf("%s/ticker/%s", baseURL, l)
+
+ resp, err := makeReq(url)
+
+ var data []Coin
+ err = json.Unmarshal(resp, &data)
+ if err != nil {
+ return nil, err
+ }
+ // creating map from the array
+ allCoins := make(map[string]Coin)
+ for i := 0; i < len(data); i++ {
+ allCoins[data[i].ID] = data[i]
+ }
+
+ return allCoins, nil
+}
+
+// GetCoinGraphData get graph data points for a crypto currency
+func GetCoinGraphData(coin string, start int64, end int64) (CoinGraph, error) {
+ url := fmt.Sprintf("%s/%s/%d/%d", coinGraphURL, strings.ToLower(coin), start*1000, end*1000)
+ resp, err := makeReq(url)
+ if err != nil {
+ return CoinGraph{}, err
+ }
+ var data CoinGraph
+ err = json.Unmarshal(resp, &data)
+ if err != nil {
+ return CoinGraph{}, err
+ }
+
+ return data, nil
+}
+
+// GetCoinPriceUSD get USD price of crypto currency
+func GetCoinPriceUSD(coin string) (float64, error) {
+ data, err := GetCoinData(strings.ToLower(coin))
+ if err != nil {
+ return float64(0), nil
+ }
+ return data.PriceUSD, nil
+}
+
+// GetCoinMarkets get market data for a coin name
+func GetCoinMarkets(coin string) ([]Market, error) {
+ url := fmt.Sprintf("https://coinmarketcap.com/currencies/%s/#markets", strings.ToLower(coin))
+ var markets []Market
+ response, err := soup.Get(url)
+ if err != nil {
+ return nil, err
+ }
+ rows := soup.HTMLParse(response).Find("table", "id", "markets-table").Find("tbody").FindAll("tr")
+ for _, row := range rows {
+ var data []string
+ for _, column := range row.FindAll("td") {
+ attrs := column.Attrs()
+ if attrs["data-sort"] != "" {
+ data = append(data, attrs["data-sort"])
+ } else {
+ data = append(data, column.Text())
+ }
+ }
+ markets = append(markets, Market{
+ Rank: toInt(data[0]),
+ Exchange: data[1],
+ Pair: data[2],
+ VolumeUSD: toFloat(data[3]),
+ Price: toFloat(data[4]),
+ VolumePercent: toFloat(data[5]),
+ Updated: data[6],
+ })
+ }
+ return markets, nil
+}
+
+// doReq HTTP client
+func doReq(req *http.Request) ([]byte, error) {
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ if 200 != resp.StatusCode {
+ return nil, fmt.Errorf("%s", body)
+ }
+
+ return body, nil
+}
+
+// makeReq HTTP request helper
+func makeReq(url string) ([]byte, error) {
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := doReq(req)
+ if err != nil {
+ return nil, err
+ }
+
+ return resp, err
+}
+
+// toInt helper for parsing strings to int
+func toInt(rawInt string) int {
+ parsed, _ := strconv.Atoi(strings.Replace(strings.Replace(rawInt, "$", "", -1), ",", "", -1))
+ return parsed
+}
+
+// toFloat helper for parsing strings to float
+func toFloat(rawFloat string) float64 {
+ parsed, _ := strconv.ParseFloat(strings.Replace(strings.Replace(strings.Replace(rawFloat, "$", "", -1), ",", "", -1), "%", "", -1), 64)
+ return parsed
+}
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/coinmarketcap_test.go b/vendor/github.com/miguelmota/go-coinmarketcap/coinmarketcap_test.go
new file mode 100644
index 0000000..8898e07
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/coinmarketcap_test.go
@@ -0,0 +1,219 @@
+package coinmarketcap
+
+import (
+ "testing"
+ "time"
+)
+
+func TestGetGlobalMarketData(t *testing.T) {
+ market, err := GetGlobalMarketData()
+ if err != nil {
+ t.FailNow()
+ }
+
+ if market.ActiveAssets == 0 {
+ t.FailNow()
+ }
+ if market.ActiveCurrencies == 0 {
+ t.FailNow()
+ }
+ if market.ActiveMarkets == 0 {
+ t.FailNow()
+ }
+ if market.BitcoinPercentageOfMarketCap == 0 {
+ t.FailNow()
+ }
+ if market.Total24HVolumeUSD == 0 {
+ t.FailNow()
+ }
+ if market.TotalMarketCapUSD == 0 {
+ t.FailNow()
+ }
+}
+
+func TestGetGlobalMarketGraphData(t *testing.T) {
+ var threeMonths int64 = (60 * 60 * 24 * 90)
+ end := time.Now().Unix()
+ start := end - threeMonths
+
+ graph, err := GetGlobalMarketGraphData(start, end)
+ if err != nil {
+ t.FailNow()
+ }
+
+ if graph.MarketCapByAvailableSupply[0][0] == 0 {
+ t.FailNow()
+ }
+
+ if graph.VolumeUSD[0][0] == 0 {
+ t.FailNow()
+ }
+}
+
+func TestGetAltcoinMarketGraphData(t *testing.T) {
+ var threeMonths int64 = (60 * 60 * 24 * 90)
+ end := time.Now().Unix()
+ start := end - threeMonths
+
+ graph, err := GetAltcoinMarketGraphData(start, end)
+ if err != nil {
+ t.FailNow()
+ }
+
+ if graph.MarketCapByAvailableSupply[0][0] == 0 {
+ t.FailNow()
+ }
+
+ if graph.VolumeUSD[0][0] == 0 {
+ t.FailNow()
+ }
+}
+
+func TestGetCoinData(t *testing.T) {
+ coin, err := GetCoinData("ethereum")
+ if err != nil {
+ t.FailNow()
+ }
+
+ if coin.AvailableSupply == 0 {
+ t.FailNow()
+ }
+ if coin.ID == "" {
+ t.FailNow()
+ }
+ if coin.LastUpdated == "" {
+ t.FailNow()
+ }
+ if coin.MarketCapUSD == 0 {
+ t.FailNow()
+ }
+ if coin.Name == "" {
+ t.FailNow()
+ }
+ if coin.PercentChange1H == 0 {
+ t.FailNow()
+ }
+ if coin.PercentChange24H == 0 {
+ t.FailNow()
+ }
+ if coin.PercentChange7D == 0 {
+ t.FailNow()
+ }
+ if coin.PriceBTC == 0 {
+ t.FailNow()
+ }
+ if coin.PriceUSD == 0 {
+ t.FailNow()
+ }
+ if coin.Rank == 0 {
+ t.FailNow()
+ }
+ if coin.Symbol == "" {
+ t.FailNow()
+ }
+ if coin.TotalSupply == 0 {
+ t.FailNow()
+ }
+ if coin.USD24HVolume == 0 {
+ t.FailNow()
+ }
+}
+
+func TestGetAllCoinData(t *testing.T) {
+ coins, err := GetAllCoinData(10)
+ if err != nil {
+ t.FailNow()
+ }
+
+ if len(coins) != 10 {
+ t.FailNow()
+ }
+}
+
+func TestGetCoinGraphData(t *testing.T) {
+ var threeMonths int64 = (60 * 60 * 24 * 90)
+ end := time.Now().Unix()
+ start := end - threeMonths
+
+ graph, err := GetCoinGraphData("ethereum", start, end)
+ if err != nil {
+ t.FailNow()
+ }
+
+ if graph.MarketCapByAvailableSupply[0][0] == 0 {
+ t.FailNow()
+ }
+ if graph.PriceBTC[0][0] == 0 {
+ t.FailNow()
+ }
+ if graph.PriceUSD[0][0] == 0 {
+ t.FailNow()
+ }
+ if graph.VolumeUSD[0][0] == 0 {
+ t.FailNow()
+ }
+}
+
+func TestGetCoinPriceUSD(t *testing.T) {
+ price, err := GetCoinPriceUSD("ethereum")
+ if err != nil {
+ t.FailNow()
+ }
+ if price <= 0 {
+ t.FailNow()
+ }
+}
+
+func TestGetCoinMarkets(t *testing.T) {
+ markets, err := GetCoinMarkets("ethereum")
+ if err != nil {
+ t.FailNow()
+ }
+ if len(markets) == 0 {
+ t.FailNow()
+ }
+
+ market := markets[0]
+ if market.Rank == 0 {
+ t.FailNow()
+ }
+ if market.Exchange == "" {
+ t.FailNow()
+ }
+ if market.Pair == "" {
+ t.FailNow()
+ }
+ if market.VolumeUSD == 0 {
+ t.FailNow()
+ }
+ if market.Price == 0 {
+ t.FailNow()
+ }
+ if market.VolumePercent == 0 {
+ t.FailNow()
+ }
+ if market.Updated == "" {
+ }
+}
+
+func TestDoReq(t *testing.T) {
+ // TODO
+}
+
+func TestMakeReq(t *testing.T) {
+ // TODO
+}
+
+func TestToInt(t *testing.T) {
+ v := toInt("5")
+ if v != 5 {
+ t.FailNow()
+ }
+}
+
+func TestToFloat(t *testing.T) {
+ v := toFloat("5.2")
+ if v != 5.2 {
+ t.FailNow()
+ }
+}
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/example/all_coins.go b/vendor/github.com/miguelmota/go-coinmarketcap/example/all_coins.go
new file mode 100644
index 0000000..33f8be6
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/example/all_coins.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+ "fmt"
+ "log"
+
+ cmc "github.com/miguelmota/go-coinmarketcap"
+)
+
+func main() {
+ // get data for all coins
+ coins, err := cmc.GetAllCoinData(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for _, coin := range coins {
+ fmt.Println(coin.Symbol, coin.PriceUSD)
+ }
+}
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/example/coin.go b/vendor/github.com/miguelmota/go-coinmarketcap/example/coin.go
new file mode 100644
index 0000000..daad6ca
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/example/coin.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+ "fmt"
+ "log"
+
+ cmc "github.com/miguelmota/go-coinmarketcap"
+)
+
+func main() {
+ // Get info about coin
+ coinInfo, err := cmc.GetCoinData("ethereum")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println(coinInfo)
+}
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/example/coin_graph.go b/vendor/github.com/miguelmota/go-coinmarketcap/example/coin_graph.go
new file mode 100644
index 0000000..1eee507
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/example/coin_graph.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "time"
+
+ cmc "github.com/miguelmota/go-coinmarketcap"
+)
+
+func main() {
+ threeMonths := int64(60 * 60 * 24 * 90)
+ now := time.Now()
+ secs := now.Unix()
+ start := secs - threeMonths
+ end := secs
+
+ // Get graph data for coin
+ coinGraphData, err := cmc.GetCoinGraphData("ethereum", start, end)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println(coinGraphData)
+}
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/example/global_market.go b/vendor/github.com/miguelmota/go-coinmarketcap/example/global_market.go
new file mode 100644
index 0000000..d9cbafd
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/example/global_market.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+ "fmt"
+ "log"
+
+ cmc "github.com/miguelmota/go-coinmarketcap"
+)
+
+func main() {
+ // Get global market data
+ marketInfo, err := cmc.GetGlobalMarketData()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println(marketInfo)
+}
diff --git a/vendor/github.com/miguelmota/go-coinmarketcap/types.go b/vendor/github.com/miguelmota/go-coinmarketcap/types.go
new file mode 100644
index 0000000..93fd77a
--- /dev/null
+++ b/vendor/github.com/miguelmota/go-coinmarketcap/types.go
@@ -0,0 +1,66 @@
+package coinmarketcap
+
+// Interface interface
+type Interface interface {
+ GetGlobalMarketData() (GlobalMarketData, error)
+ GetGlobalMarketGraphData(start int64, end int64) (MarketGraph, error)
+ GetAltcoinMarketGraphData(start int64, end int64) (MarketGraph, error)
+ GetCoinData(coin string) (Coin, error)
+ GetAllCoinData(limit int) (map[string]Coin, error)
+ GetCoinGraphData(coin string, start int64, end int64) (CoinGraph, error)
+ GetCoinPriceUSD(coin string) (float64, error)
+ GetCoinMarkets(coin string) ([]Market, error)
+}
+
+// Coin struct
+type Coin struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Symbol string `json:"symbol"`
+ Rank int `json:"rank,string"`
+ PriceUSD float64 `json:"price_usd,string"`
+ PriceBTC float64 `json:"price_btc,string"`
+ USD24HVolume float64 `json:"24h_volume_usd,string"`
+ MarketCapUSD float64 `json:"market_cap_usd,string"`
+ AvailableSupply float64 `json:"available_supply,string"`
+ TotalSupply float64 `json:"total_supply,string"`
+ PercentChange1H float64 `json:"percent_change_1h,string"`
+ PercentChange24H float64 `json:"percent_change_24h,string"`
+ PercentChange7D float64 `json:"percent_change_7d,string"`
+ LastUpdated string `json:"last_updated"`
+}
+
+// GlobalMarketData struct
+type GlobalMarketData struct {
+ TotalMarketCapUSD float64 `json:"total_market_cap_usd"`
+ Total24HVolumeUSD float64 `json:"total_24h_volume_usd"`
+ BitcoinPercentageOfMarketCap float64 `json:"bitcoin_percentage_of_market_cap"`
+ ActiveCurrencies int `json:"active_currencies"`
+ ActiveAssets int `json:"active_assets"`
+ ActiveMarkets int `json:"active_markets"`
+}
+
+// CoinGraph struct
+type CoinGraph struct {
+ MarketCapByAvailableSupply [][]float64 `json:"market_cap_by_available_supply"`
+ PriceBTC [][]float64 `json:"price_btc"`
+ PriceUSD [][]float64 `json:"price_usd"`
+ VolumeUSD [][]float64 `json:"volume_usd"`
+}
+
+// Market struct
+type Market struct {
+ Rank int
+ Exchange string
+ Pair string
+ VolumeUSD float64
+ Price float64
+ VolumePercent float64
+ Updated string
+}
+
+// MarketGraph struct
+type MarketGraph struct {
+ MarketCapByAvailableSupply [][]float64 `json:"market_cap_by_available_supply"`
+ VolumeUSD [][]float64 `json:"volume_usd"`
+}
diff --git a/vendor/github.com/nsf/termbox-go/AUTHORS b/vendor/github.com/nsf/termbox-go/AUTHORS
new file mode 100644
index 0000000..fe26fb0
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/AUTHORS
@@ -0,0 +1,4 @@
+# Please keep this file sorted.
+
+Georg Reinke
+nsf
diff --git a/vendor/github.com/nsf/termbox-go/LICENSE b/vendor/github.com/nsf/termbox-go/LICENSE
new file mode 100644
index 0000000..d9bc068
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2012 termbox-go authors
+
+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.
diff --git a/vendor/github.com/nsf/termbox-go/README.md b/vendor/github.com/nsf/termbox-go/README.md
new file mode 100644
index 0000000..40332bb
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/README.md
@@ -0,0 +1,39 @@
+[![GoDoc](https://godoc.org/github.com/nsf/termbox-go?status.svg)](http://godoc.org/github.com/nsf/termbox-go)
+
+## Termbox
+Termbox is a library that provides a minimalistic API which allows the programmer to write text-based user interfaces. The library is crossplatform and has both terminal-based implementations on *nix operating systems and a winapi console based implementation for windows operating systems. The basic idea is an abstraction of the greatest common subset of features available on all major terminals and other terminal-like APIs in a minimalistic fashion. Small API means it is easy to implement, test, maintain and learn it, that's what makes the termbox a distinct library in its area.
+
+### Installation
+Install and update this go package with `go get -u github.com/nsf/termbox-go`
+
+### Examples
+For examples of what can be done take a look at demos in the _demos directory. You can try them with go run: `go run _demos/keyboard.go`
+
+There are also some interesting projects using termbox-go:
+ - [godit](https://github.com/nsf/godit) is an emacsish lightweight text editor written using termbox.
+ - [gomatrix](https://github.com/GeertJohan/gomatrix) connects to The Matrix and displays its data streams in your terminal.
+ - [gotetris](https://github.com/jjinux/gotetris) is an implementation of Tetris.
+ - [sokoban-go](https://github.com/rn2dy/sokoban-go) is an implementation of sokoban game.
+ - [hecate](https://github.com/evanmiller/hecate) is a hex editor designed by Satan.
+ - [httopd](https://github.com/verdverm/httopd) is top for httpd logs.
+ - [mop](https://github.com/mop-tracker/mop) is stock market tracker for hackers.
+ - [termui](https://github.com/gizak/termui) is a terminal dashboard.
+ - [termloop](https://github.com/JoelOtter/termloop) is a terminal game engine.
+ - [xterm-color-chart](https://github.com/kutuluk/xterm-color-chart) is a XTerm 256 color chart.
+ - [gocui](https://github.com/jroimartin/gocui) is a minimalist Go library aimed at creating console user interfaces.
+ - [dry](https://github.com/moncho/dry) is an interactive cli to manage Docker containers.
+ - [pxl](https://github.com/ichinaski/pxl) displays images in the terminal.
+ - [snake-game](https://github.com/DyegoCosta/snake-game) is an implementation of the Snake game.
+ - [gone](https://github.com/guillaumebreton/gone) is a CLI pomodoro® timer.
+ - [Spoof.go](https://github.com/sabey/spoofgo) controllable movement spoofing from the cli
+ - [lf](https://github.com/gokcehan/lf) is a terminal file manager
+ - [rat](https://github.com/ericfreese/rat) lets you compose shell commands to build terminal applications.
+ - [httplab](https://github.com/gchaincl/httplab) An interactive web server.
+ - [tetris](https://github.com/MichaelS11/tetris) Go Tetris with AI option
+ - [wot](https://github.com/kyu-suke/wot) Wait time during command is completed.
+ - [2048-go](https://github.com/1984weed/2048-go) is 2048 in Go
+ - [jv](https://github.com/maxzender/jv) helps you view JSON on the command-line.
+ - [pinger](https://github.com/hirose31/pinger) helps you to monitor numerous hosts using ICMP ECHO_REQUEST.
+ - [vixl44](https://github.com/sebashwa/vixl44) lets you create pixel art inside your terminal using vim movements
+ - [zterm](https://github.com/varunrau/zterm) is a typing game inspired by http://zty.pe/
+ - [cointop](https://github.com/miguelmota/cointop) is an interactive terminal based UI application for tracking cryptocurrencies.
diff --git a/vendor/github.com/nsf/termbox-go/_demos/editbox.go b/vendor/github.com/nsf/termbox-go/_demos/editbox.go
new file mode 100644
index 0000000..e429080
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/_demos/editbox.go
@@ -0,0 +1,300 @@
+package main
+
+import (
+ "github.com/mattn/go-runewidth"
+ "github.com/nsf/termbox-go"
+ "unicode/utf8"
+)
+
+func tbprint(x, y int, fg, bg termbox.Attribute, msg string) {
+ for _, c := range msg {
+ termbox.SetCell(x, y, c, fg, bg)
+ x += runewidth.RuneWidth(c)
+ }
+}
+
+func fill(x, y, w, h int, cell termbox.Cell) {
+ for ly := 0; ly < h; ly++ {
+ for lx := 0; lx < w; lx++ {
+ termbox.SetCell(x+lx, y+ly, cell.Ch, cell.Fg, cell.Bg)
+ }
+ }
+}
+
+func rune_advance_len(r rune, pos int) int {
+ if r == '\t' {
+ return tabstop_length - pos%tabstop_length
+ }
+ return runewidth.RuneWidth(r)
+}
+
+func voffset_coffset(text []byte, boffset int) (voffset, coffset int) {
+ text = text[:boffset]
+ for len(text) > 0 {
+ r, size := utf8.DecodeRune(text)
+ text = text[size:]
+ coffset += 1
+ voffset += rune_advance_len(r, voffset)
+ }
+ return
+}
+
+func byte_slice_grow(s []byte, desired_cap int) []byte {
+ if cap(s) < desired_cap {
+ ns := make([]byte, len(s), desired_cap)
+ copy(ns, s)
+ return ns
+ }
+ return s
+}
+
+func byte_slice_remove(text []byte, from, to int) []byte {
+ size := to - from
+ copy(text[from:], text[to:])
+ text = text[:len(text)-size]
+ return text
+}
+
+func byte_slice_insert(text []byte, offset int, what []byte) []byte {
+ n := len(text) + len(what)
+ text = byte_slice_grow(text, n)
+ text = text[:n]
+ copy(text[offset+len(what):], text[offset:])
+ copy(text[offset:], what)
+ return text
+}
+
+const preferred_horizontal_threshold = 5
+const tabstop_length = 8
+
+type EditBox struct {
+ text []byte
+ line_voffset int
+ cursor_boffset int // cursor offset in bytes
+ cursor_voffset int // visual cursor offset in termbox cells
+ cursor_coffset int // cursor offset in unicode code points
+}
+
+// Draws the EditBox in the given location, 'h' is not used at the moment
+func (eb *EditBox) Draw(x, y, w, h int) {
+ eb.AdjustVOffset(w)
+
+ const coldef = termbox.ColorDefault
+ fill(x, y, w, h, termbox.Cell{Ch: ' '})
+
+ t := eb.text
+ lx := 0
+ tabstop := 0
+ for {
+ rx := lx - eb.line_voffset
+ if len(t) == 0 {
+ break
+ }
+
+ if lx == tabstop {
+ tabstop += tabstop_length
+ }
+
+ if rx >= w {
+ termbox.SetCell(x+w-1, y, '→',
+ coldef, coldef)
+ break
+ }
+
+ r, size := utf8.DecodeRune(t)
+ if r == '\t' {
+ for ; lx < tabstop; lx++ {
+ rx = lx - eb.line_voffset
+ if rx >= w {
+ goto next
+ }
+
+ if rx >= 0 {
+ termbox.SetCell(x+rx, y, ' ', coldef, coldef)
+ }
+ }
+ } else {
+ if rx >= 0 {
+ termbox.SetCell(x+rx, y, r, coldef, coldef)
+ }
+ lx += runewidth.RuneWidth(r)
+ }
+ next:
+ t = t[size:]
+ }
+
+ if eb.line_voffset != 0 {
+ termbox.SetCell(x, y, '←', coldef, coldef)
+ }
+}
+
+// Adjusts line visual offset to a proper value depending on width
+func (eb *EditBox) AdjustVOffset(width int) {
+ ht := preferred_horizontal_threshold
+ max_h_threshold := (width - 1) / 2
+ if ht > max_h_threshold {
+ ht = max_h_threshold
+ }
+
+ threshold := width - 1
+ if eb.line_voffset != 0 {
+ threshold = width - ht
+ }
+ if eb.cursor_voffset-eb.line_voffset >= threshold {
+ eb.line_voffset = eb.cursor_voffset + (ht - width + 1)
+ }
+
+ if eb.line_voffset != 0 && eb.cursor_voffset-eb.line_voffset < ht {
+ eb.line_voffset = eb.cursor_voffset - ht
+ if eb.line_voffset < 0 {
+ eb.line_voffset = 0
+ }
+ }
+}
+
+func (eb *EditBox) MoveCursorTo(boffset int) {
+ eb.cursor_boffset = boffset
+ eb.cursor_voffset, eb.cursor_coffset = voffset_coffset(eb.text, boffset)
+}
+
+func (eb *EditBox) RuneUnderCursor() (rune, int) {
+ return utf8.DecodeRune(eb.text[eb.cursor_boffset:])
+}
+
+func (eb *EditBox) RuneBeforeCursor() (rune, int) {
+ return utf8.DecodeLastRune(eb.text[:eb.cursor_boffset])
+}
+
+func (eb *EditBox) MoveCursorOneRuneBackward() {
+ if eb.cursor_boffset == 0 {
+ return
+ }
+ _, size := eb.RuneBeforeCursor()
+ eb.MoveCursorTo(eb.cursor_boffset - size)
+}
+
+func (eb *EditBox) MoveCursorOneRuneForward() {
+ if eb.cursor_boffset == len(eb.text) {
+ return
+ }
+ _, size := eb.RuneUnderCursor()
+ eb.MoveCursorTo(eb.cursor_boffset + size)
+}
+
+func (eb *EditBox) MoveCursorToBeginningOfTheLine() {
+ eb.MoveCursorTo(0)
+}
+
+func (eb *EditBox) MoveCursorToEndOfTheLine() {
+ eb.MoveCursorTo(len(eb.text))
+}
+
+func (eb *EditBox) DeleteRuneBackward() {
+ if eb.cursor_boffset == 0 {
+ return
+ }
+
+ eb.MoveCursorOneRuneBackward()
+ _, size := eb.RuneUnderCursor()
+ eb.text = byte_slice_remove(eb.text, eb.cursor_boffset, eb.cursor_boffset+size)
+}
+
+func (eb *EditBox) DeleteRuneForward() {
+ if eb.cursor_boffset == len(eb.text) {
+ return
+ }
+ _, size := eb.RuneUnderCursor()
+ eb.text = byte_slice_remove(eb.text, eb.cursor_boffset, eb.cursor_boffset+size)
+}
+
+func (eb *EditBox) DeleteTheRestOfTheLine() {
+ eb.text = eb.text[:eb.cursor_boffset]
+}
+
+func (eb *EditBox) InsertRune(r rune) {
+ var buf [utf8.UTFMax]byte
+ n := utf8.EncodeRune(buf[:], r)
+ eb.text = byte_slice_insert(eb.text, eb.cursor_boffset, buf[:n])
+ eb.MoveCursorOneRuneForward()
+}
+
+// Please, keep in mind that cursor depends on the value of line_voffset, which
+// is being set on Draw() call, so.. call this method after Draw() one.
+func (eb *EditBox) CursorX() int {
+ return eb.cursor_voffset - eb.line_voffset
+}
+
+var edit_box EditBox
+
+const edit_box_width = 30
+
+func redraw_all() {
+ const coldef = termbox.ColorDefault
+ termbox.Clear(coldef, coldef)
+ w, h := termbox.Size()
+
+ midy := h / 2
+ midx := (w - edit_box_width) / 2
+
+ // unicode box drawing chars around the edit box
+ termbox.SetCell(midx-1, midy, '│', coldef, coldef)
+ termbox.SetCell(midx+edit_box_width, midy, '│', coldef, coldef)
+ termbox.SetCell(midx-1, midy-1, '┌', coldef, coldef)
+ termbox.SetCell(midx-1, midy+1, '└', coldef, coldef)
+ termbox.SetCell(midx+edit_box_width, midy-1, '┐', coldef, coldef)
+ termbox.SetCell(midx+edit_box_width, midy+1, '┘', coldef, coldef)
+ fill(midx, midy-1, edit_box_width, 1, termbox.Cell{Ch: '─'})
+ fill(midx, midy+1, edit_box_width, 1, termbox.Cell{Ch: '─'})
+
+ edit_box.Draw(midx, midy, edit_box_width, 1)
+ termbox.SetCursor(midx+edit_box.CursorX(), midy)
+
+ tbprint(midx+6, midy+3, coldef, coldef, "Press ESC to quit")
+ termbox.Flush()
+}
+
+func main() {
+ err := termbox.Init()
+ if err != nil {
+ panic(err)
+ }
+ defer termbox.Close()
+ termbox.SetInputMode(termbox.InputEsc)
+
+ redraw_all()
+mainloop:
+ for {
+ switch ev := termbox.PollEvent(); ev.Type {
+ case termbox.EventKey:
+ switch ev.Key {
+ case termbox.KeyEsc:
+ break mainloop
+ case termbox.KeyArrowLeft, termbox.KeyCtrlB:
+ edit_box.MoveCursorOneRuneBackward()
+ case termbox.KeyArrowRight, termbox.KeyCtrlF:
+ edit_box.MoveCursorOneRuneForward()
+ case termbox.KeyBackspace, termbox.KeyBackspace2:
+ edit_box.DeleteRuneBackward()
+ case termbox.KeyDelete, termbox.KeyCtrlD:
+ edit_box.DeleteRuneForward()
+ case termbox.KeyTab:
+ edit_box.InsertRune('\t')
+ case termbox.KeySpace:
+ edit_box.InsertRune(' ')
+ case termbox.KeyCtrlK:
+ edit_box.DeleteTheRestOfTheLine()
+ case termbox.KeyHome, termbox.KeyCtrlA:
+ edit_box.MoveCursorToBeginningOfTheLine()
+ case termbox.KeyEnd, termbox.KeyCtrlE:
+ edit_box.MoveCursorToEndOfTheLine()
+ default:
+ if ev.Ch != 0 {
+ edit_box.InsertRune(ev.Ch)
+ }
+ }
+ case termbox.EventError:
+ panic(ev.Err)
+ }
+ redraw_all()
+ }
+}
diff --git a/vendor/github.com/nsf/termbox-go/_demos/interrupt.go b/vendor/github.com/nsf/termbox-go/_demos/interrupt.go
new file mode 100644
index 0000000..5534521
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/_demos/interrupt.go
@@ -0,0 +1,69 @@
+package main
+
+import (
+ "fmt"
+ "github.com/nsf/termbox-go"
+ "time"
+)
+
+func tbPrint(x, y int, fg, bg termbox.Attribute, msg string) {
+ for _, c := range msg {
+ termbox.SetCell(x, y, c, fg, bg)
+ x++
+ }
+}
+
+func draw(i int) {
+ termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
+ defer termbox.Flush()
+
+ w, h := termbox.Size()
+ s := fmt.Sprintf("count = %d", i)
+
+ tbPrint((w/2)-(len(s)/2), h/2, termbox.ColorRed, termbox.ColorDefault, s)
+}
+
+func main() {
+ err := termbox.Init()
+ if err != nil {
+ panic(err)
+ }
+ termbox.SetInputMode(termbox.InputEsc)
+
+ go func() {
+ time.Sleep(5 * time.Second)
+ termbox.Interrupt()
+
+ // This should never run - the Interrupt(), above, should cause the event
+ // loop below to exit, which then exits the process. If something goes
+ // wrong, this panic will trigger and show what happened.
+ time.Sleep(1 * time.Second)
+ panic("this should never run")
+ }()
+
+ var count int
+
+ draw(count)
+mainloop:
+ for {
+ switch ev := termbox.PollEvent(); ev.Type {
+ case termbox.EventKey:
+ if ev.Ch == '+' {
+ count++
+ } else if ev.Ch == '-' {
+ count--
+ }
+
+ case termbox.EventError:
+ panic(ev.Err)
+
+ case termbox.EventInterrupt:
+ break mainloop
+ }
+
+ draw(count)
+ }
+ termbox.Close()
+
+ fmt.Println("Finished")
+}
diff --git a/vendor/github.com/nsf/termbox-go/_demos/keyboard.go b/vendor/github.com/nsf/termbox-go/_demos/keyboard.go
new file mode 100644
index 0000000..b6a258e
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/_demos/keyboard.go
@@ -0,0 +1,722 @@
+package main
+
+import "github.com/nsf/termbox-go"
+import "fmt"
+
+type key struct {
+ x int
+ y int
+ ch rune
+}
+
+var K_ESC = []key{{1, 1, 'E'}, {2, 1, 'S'}, {3, 1, 'C'}}
+var K_F1 = []key{{6, 1, 'F'}, {7, 1, '1'}}
+var K_F2 = []key{{9, 1, 'F'}, {10, 1, '2'}}
+var K_F3 = []key{{12, 1, 'F'}, {13, 1, '3'}}
+var K_F4 = []key{{15, 1, 'F'}, {16, 1, '4'}}
+var K_F5 = []key{{19, 1, 'F'}, {20, 1, '5'}}
+var K_F6 = []key{{22, 1, 'F'}, {23, 1, '6'}}
+var K_F7 = []key{{25, 1, 'F'}, {26, 1, '7'}}
+var K_F8 = []key{{28, 1, 'F'}, {29, 1, '8'}}
+var K_F9 = []key{{33, 1, 'F'}, {34, 1, '9'}}
+var K_F10 = []key{{36, 1, 'F'}, {37, 1, '1'}, {38, 1, '0'}}
+var K_F11 = []key{{40, 1, 'F'}, {41, 1, '1'}, {42, 1, '1'}}
+var K_F12 = []key{{44, 1, 'F'}, {45, 1, '1'}, {46, 1, '2'}}
+var K_PRN = []key{{50, 1, 'P'}, {51, 1, 'R'}, {52, 1, 'N'}}
+var K_SCR = []key{{54, 1, 'S'}, {55, 1, 'C'}, {56, 1, 'R'}}
+var K_BRK = []key{{58, 1, 'B'}, {59, 1, 'R'}, {60, 1, 'K'}}
+var K_LED1 = []key{{66, 1, '-'}}
+var K_LED2 = []key{{70, 1, '-'}}
+var K_LED3 = []key{{74, 1, '-'}}
+var K_TILDE = []key{{1, 4, '`'}}
+var K_TILDE_SHIFT = []key{{1, 4, '~'}}
+var K_1 = []key{{4, 4, '1'}}
+var K_1_SHIFT = []key{{4, 4, '!'}}
+var K_2 = []key{{7, 4, '2'}}
+var K_2_SHIFT = []key{{7, 4, '@'}}
+var K_3 = []key{{10, 4, '3'}}
+var K_3_SHIFT = []key{{10, 4, '#'}}
+var K_4 = []key{{13, 4, '4'}}
+var K_4_SHIFT = []key{{13, 4, '$'}}
+var K_5 = []key{{16, 4, '5'}}
+var K_5_SHIFT = []key{{16, 4, '%'}}
+var K_6 = []key{{19, 4, '6'}}
+var K_6_SHIFT = []key{{19, 4, '^'}}
+var K_7 = []key{{22, 4, '7'}}
+var K_7_SHIFT = []key{{22, 4, '&'}}
+var K_8 = []key{{25, 4, '8'}}
+var K_8_SHIFT = []key{{25, 4, '*'}}
+var K_9 = []key{{28, 4, '9'}}
+var K_9_SHIFT = []key{{28, 4, '('}}
+var K_0 = []key{{31, 4, '0'}}
+var K_0_SHIFT = []key{{31, 4, ')'}}
+var K_MINUS = []key{{34, 4, '-'}}
+var K_MINUS_SHIFT = []key{{34, 4, '_'}}
+var K_EQUALS = []key{{37, 4, '='}}
+var K_EQUALS_SHIFT = []key{{37, 4, '+'}}
+var K_BACKSLASH = []key{{40, 4, '\\'}}
+var K_BACKSLASH_SHIFT = []key{{40, 4, '|'}}
+var K_BACKSPACE = []key{{44, 4, 0x2190}, {45, 4, 0x2500}, {46, 4, 0x2500}}
+var K_INS = []key{{50, 4, 'I'}, {51, 4, 'N'}, {52, 4, 'S'}}
+var K_HOM = []key{{54, 4, 'H'}, {55, 4, 'O'}, {56, 4, 'M'}}
+var K_PGU = []key{{58, 4, 'P'}, {59, 4, 'G'}, {60, 4, 'U'}}
+var K_K_NUMLOCK = []key{{65, 4, 'N'}}
+var K_K_SLASH = []key{{68, 4, '/'}}
+var K_K_STAR = []key{{71, 4, '*'}}
+var K_K_MINUS = []key{{74, 4, '-'}}
+var K_TAB = []key{{1, 6, 'T'}, {2, 6, 'A'}, {3, 6, 'B'}}
+var K_q = []key{{6, 6, 'q'}}
+var K_Q = []key{{6, 6, 'Q'}}
+var K_w = []key{{9, 6, 'w'}}
+var K_W = []key{{9, 6, 'W'}}
+var K_e = []key{{12, 6, 'e'}}
+var K_E = []key{{12, 6, 'E'}}
+var K_r = []key{{15, 6, 'r'}}
+var K_R = []key{{15, 6, 'R'}}
+var K_t = []key{{18, 6, 't'}}
+var K_T = []key{{18, 6, 'T'}}
+var K_y = []key{{21, 6, 'y'}}
+var K_Y = []key{{21, 6, 'Y'}}
+var K_u = []key{{24, 6, 'u'}}
+var K_U = []key{{24, 6, 'U'}}
+var K_i = []key{{27, 6, 'i'}}
+var K_I = []key{{27, 6, 'I'}}
+var K_o = []key{{30, 6, 'o'}}
+var K_O = []key{{30, 6, 'O'}}
+var K_p = []key{{33, 6, 'p'}}
+var K_P = []key{{33, 6, 'P'}}
+var K_LSQB = []key{{36, 6, '['}}
+var K_LCUB = []key{{36, 6, '{'}}
+var K_RSQB = []key{{39, 6, ']'}}
+var K_RCUB = []key{{39, 6, '}'}}
+var K_ENTER = []key{
+ {43, 6, 0x2591}, {44, 6, 0x2591}, {45, 6, 0x2591}, {46, 6, 0x2591},
+ {43, 7, 0x2591}, {44, 7, 0x2591}, {45, 7, 0x21B5}, {46, 7, 0x2591},
+ {41, 8, 0x2591}, {42, 8, 0x2591}, {43, 8, 0x2591}, {44, 8, 0x2591},
+ {45, 8, 0x2591}, {46, 8, 0x2591},
+}
+var K_DEL = []key{{50, 6, 'D'}, {51, 6, 'E'}, {52, 6, 'L'}}
+var K_END = []key{{54, 6, 'E'}, {55, 6, 'N'}, {56, 6, 'D'}}
+var K_PGD = []key{{58, 6, 'P'}, {59, 6, 'G'}, {60, 6, 'D'}}
+var K_K_7 = []key{{65, 6, '7'}}
+var K_K_8 = []key{{68, 6, '8'}}
+var K_K_9 = []key{{71, 6, '9'}}
+var K_K_PLUS = []key{{74, 6, ' '}, {74, 7, '+'}, {74, 8, ' '}}
+var K_CAPS = []key{{1, 8, 'C'}, {2, 8, 'A'}, {3, 8, 'P'}, {4, 8, 'S'}}
+var K_a = []key{{7, 8, 'a'}}
+var K_A = []key{{7, 8, 'A'}}
+var K_s = []key{{10, 8, 's'}}
+var K_S = []key{{10, 8, 'S'}}
+var K_d = []key{{13, 8, 'd'}}
+var K_D = []key{{13, 8, 'D'}}
+var K_f = []key{{16, 8, 'f'}}
+var K_F = []key{{16, 8, 'F'}}
+var K_g = []key{{19, 8, 'g'}}
+var K_G = []key{{19, 8, 'G'}}
+var K_h = []key{{22, 8, 'h'}}
+var K_H = []key{{22, 8, 'H'}}
+var K_j = []key{{25, 8, 'j'}}
+var K_J = []key{{25, 8, 'J'}}
+var K_k = []key{{28, 8, 'k'}}
+var K_K = []key{{28, 8, 'K'}}
+var K_l = []key{{31, 8, 'l'}}
+var K_L = []key{{31, 8, 'L'}}
+var K_SEMICOLON = []key{{34, 8, ';'}}
+var K_PARENTHESIS = []key{{34, 8, ':'}}
+var K_QUOTE = []key{{37, 8, '\''}}
+var K_DOUBLEQUOTE = []key{{37, 8, '"'}}
+var K_K_4 = []key{{65, 8, '4'}}
+var K_K_5 = []key{{68, 8, '5'}}
+var K_K_6 = []key{{71, 8, '6'}}
+var K_LSHIFT = []key{{1, 10, 'S'}, {2, 10, 'H'}, {3, 10, 'I'}, {4, 10, 'F'}, {5, 10, 'T'}}
+var K_z = []key{{9, 10, 'z'}}
+var K_Z = []key{{9, 10, 'Z'}}
+var K_x = []key{{12, 10, 'x'}}
+var K_X = []key{{12, 10, 'X'}}
+var K_c = []key{{15, 10, 'c'}}
+var K_C = []key{{15, 10, 'C'}}
+var K_v = []key{{18, 10, 'v'}}
+var K_V = []key{{18, 10, 'V'}}
+var K_b = []key{{21, 10, 'b'}}
+var K_B = []key{{21, 10, 'B'}}
+var K_n = []key{{24, 10, 'n'}}
+var K_N = []key{{24, 10, 'N'}}
+var K_m = []key{{27, 10, 'm'}}
+var K_M = []key{{27, 10, 'M'}}
+var K_COMMA = []key{{30, 10, ','}}
+var K_LANB = []key{{30, 10, '<'}}
+var K_PERIOD = []key{{33, 10, '.'}}
+var K_RANB = []key{{33, 10, '>'}}
+var K_SLASH = []key{{36, 10, '/'}}
+var K_QUESTION = []key{{36, 10, '?'}}
+var K_RSHIFT = []key{{42, 10, 'S'}, {43, 10, 'H'}, {44, 10, 'I'}, {45, 10, 'F'}, {46, 10, 'T'}}
+var K_ARROW_UP = []key{{54, 10, '('}, {55, 10, 0x2191}, {56, 10, ')'}}
+var K_K_1 = []key{{65, 10, '1'}}
+var K_K_2 = []key{{68, 10, '2'}}
+var K_K_3 = []key{{71, 10, '3'}}
+var K_K_ENTER = []key{{74, 10, 0x2591}, {74, 11, 0x2591}, {74, 12, 0x2591}}
+var K_LCTRL = []key{{1, 12, 'C'}, {2, 12, 'T'}, {3, 12, 'R'}, {4, 12, 'L'}}
+var K_LWIN = []key{{6, 12, 'W'}, {7, 12, 'I'}, {8, 12, 'N'}}
+var K_LALT = []key{{10, 12, 'A'}, {11, 12, 'L'}, {12, 12, 'T'}}
+var K_SPACE = []key{
+ {14, 12, ' '}, {15, 12, ' '}, {16, 12, ' '}, {17, 12, ' '}, {18, 12, ' '},
+ {19, 12, 'S'}, {20, 12, 'P'}, {21, 12, 'A'}, {22, 12, 'C'}, {23, 12, 'E'},
+ {24, 12, ' '}, {25, 12, ' '}, {26, 12, ' '}, {27, 12, ' '}, {28, 12, ' '},
+}
+var K_RALT = []key{{30, 12, 'A'}, {31, 12, 'L'}, {32, 12, 'T'}}
+var K_RWIN = []key{{34, 12, 'W'}, {35, 12, 'I'}, {36, 12, 'N'}}
+var K_RPROP = []key{{38, 12, 'P'}, {39, 12, 'R'}, {40, 12, 'O'}, {41, 12, 'P'}}
+var K_RCTRL = []key{{43, 12, 'C'}, {44, 12, 'T'}, {45, 12, 'R'}, {46, 12, 'L'}}
+var K_ARROW_LEFT = []key{{50, 12, '('}, {51, 12, 0x2190}, {52, 12, ')'}}
+var K_ARROW_DOWN = []key{{54, 12, '('}, {55, 12, 0x2193}, {56, 12, ')'}}
+var K_ARROW_RIGHT = []key{{58, 12, '('}, {59, 12, 0x2192}, {60, 12, ')'}}
+var K_K_0 = []key{{65, 12, ' '}, {66, 12, '0'}, {67, 12, ' '}, {68, 12, ' '}}
+var K_K_PERIOD = []key{{71, 12, '.'}}
+
+type combo struct {
+ keys [][]key
+}
+
+var combos = []combo{
+ {[][]key{K_TILDE, K_2, K_SPACE, K_LCTRL, K_RCTRL}},
+ {[][]key{K_A, K_LCTRL, K_RCTRL}},
+ {[][]key{K_B, K_LCTRL, K_RCTRL}},
+ {[][]key{K_C, K_LCTRL, K_RCTRL}},
+ {[][]key{K_D, K_LCTRL, K_RCTRL}},
+ {[][]key{K_E, K_LCTRL, K_RCTRL}},
+ {[][]key{K_F, K_LCTRL, K_RCTRL}},
+ {[][]key{K_G, K_LCTRL, K_RCTRL}},
+ {[][]key{K_H, K_BACKSPACE, K_LCTRL, K_RCTRL}},
+ {[][]key{K_I, K_TAB, K_LCTRL, K_RCTRL}},
+ {[][]key{K_J, K_LCTRL, K_RCTRL}},
+ {[][]key{K_K, K_LCTRL, K_RCTRL}},
+ {[][]key{K_L, K_LCTRL, K_RCTRL}},
+ {[][]key{K_M, K_ENTER, K_K_ENTER, K_LCTRL, K_RCTRL}},
+ {[][]key{K_N, K_LCTRL, K_RCTRL}},
+ {[][]key{K_O, K_LCTRL, K_RCTRL}},
+ {[][]key{K_P, K_LCTRL, K_RCTRL}},
+ {[][]key{K_Q, K_LCTRL, K_RCTRL}},
+ {[][]key{K_R, K_LCTRL, K_RCTRL}},
+ {[][]key{K_S, K_LCTRL, K_RCTRL}},
+ {[][]key{K_T, K_LCTRL, K_RCTRL}},
+ {[][]key{K_U, K_LCTRL, K_RCTRL}},
+ {[][]key{K_V, K_LCTRL, K_RCTRL}},
+ {[][]key{K_W, K_LCTRL, K_RCTRL}},
+ {[][]key{K_X, K_LCTRL, K_RCTRL}},
+ {[][]key{K_Y, K_LCTRL, K_RCTRL}},
+ {[][]key{K_Z, K_LCTRL, K_RCTRL}},
+ {[][]key{K_LSQB, K_ESC, K_3, K_LCTRL, K_RCTRL}},
+ {[][]key{K_4, K_BACKSLASH, K_LCTRL, K_RCTRL}},
+ {[][]key{K_RSQB, K_5, K_LCTRL, K_RCTRL}},
+ {[][]key{K_6, K_LCTRL, K_RCTRL}},
+ {[][]key{K_7, K_SLASH, K_MINUS_SHIFT, K_LCTRL, K_RCTRL}},
+ {[][]key{K_SPACE}},
+ {[][]key{K_1_SHIFT, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_DOUBLEQUOTE, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_3_SHIFT, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_4_SHIFT, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_5_SHIFT, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_7_SHIFT, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_QUOTE}},
+ {[][]key{K_9_SHIFT, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_0_SHIFT, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_8_SHIFT, K_K_STAR, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_EQUALS_SHIFT, K_K_PLUS, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_COMMA}},
+ {[][]key{K_MINUS, K_K_MINUS}},
+ {[][]key{K_PERIOD, K_K_PERIOD}},
+ {[][]key{K_SLASH, K_K_SLASH}},
+ {[][]key{K_0, K_K_0}},
+ {[][]key{K_1, K_K_1}},
+ {[][]key{K_2, K_K_2}},
+ {[][]key{K_3, K_K_3}},
+ {[][]key{K_4, K_K_4}},
+ {[][]key{K_5, K_K_5}},
+ {[][]key{K_6, K_K_6}},
+ {[][]key{K_7, K_K_7}},
+ {[][]key{K_8, K_K_8}},
+ {[][]key{K_9, K_K_9}},
+ {[][]key{K_PARENTHESIS, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_SEMICOLON}},
+ {[][]key{K_LANB, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_EQUALS}},
+ {[][]key{K_RANB, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_QUESTION, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_2_SHIFT, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_A, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_B, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_C, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_D, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_E, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_F, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_G, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_H, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_I, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_J, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_K, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_L, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_M, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_N, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_O, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_P, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_Q, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_R, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_S, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_T, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_U, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_V, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_W, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_X, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_Y, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_Z, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_LSQB}},
+ {[][]key{K_BACKSLASH}},
+ {[][]key{K_RSQB}},
+ {[][]key{K_6_SHIFT, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_MINUS_SHIFT, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_TILDE}},
+ {[][]key{K_a}},
+ {[][]key{K_b}},
+ {[][]key{K_c}},
+ {[][]key{K_d}},
+ {[][]key{K_e}},
+ {[][]key{K_f}},
+ {[][]key{K_g}},
+ {[][]key{K_h}},
+ {[][]key{K_i}},
+ {[][]key{K_j}},
+ {[][]key{K_k}},
+ {[][]key{K_l}},
+ {[][]key{K_m}},
+ {[][]key{K_n}},
+ {[][]key{K_o}},
+ {[][]key{K_p}},
+ {[][]key{K_q}},
+ {[][]key{K_r}},
+ {[][]key{K_s}},
+ {[][]key{K_t}},
+ {[][]key{K_u}},
+ {[][]key{K_v}},
+ {[][]key{K_w}},
+ {[][]key{K_x}},
+ {[][]key{K_y}},
+ {[][]key{K_z}},
+ {[][]key{K_LCUB, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_BACKSLASH_SHIFT, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_RCUB, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_TILDE_SHIFT, K_LSHIFT, K_RSHIFT}},
+ {[][]key{K_8, K_BACKSPACE, K_LCTRL, K_RCTRL}},
+}
+
+var func_combos = []combo{
+ {[][]key{K_F1}},
+ {[][]key{K_F2}},
+ {[][]key{K_F3}},
+ {[][]key{K_F4}},
+ {[][]key{K_F5}},
+ {[][]key{K_F6}},
+ {[][]key{K_F7}},
+ {[][]key{K_F8}},
+ {[][]key{K_F9}},
+ {[][]key{K_F10}},
+ {[][]key{K_F11}},
+ {[][]key{K_F12}},
+ {[][]key{K_INS}},
+ {[][]key{K_DEL}},
+ {[][]key{K_HOM}},
+ {[][]key{K_END}},
+ {[][]key{K_PGU}},
+ {[][]key{K_PGD}},
+ {[][]key{K_ARROW_UP}},
+ {[][]key{K_ARROW_DOWN}},
+ {[][]key{K_ARROW_LEFT}},
+ {[][]key{K_ARROW_RIGHT}},
+}
+
+func print_tb(x, y int, fg, bg termbox.Attribute, msg string) {
+ for _, c := range msg {
+ termbox.SetCell(x, y, c, fg, bg)
+ x++
+ }
+}
+
+func printf_tb(x, y int, fg, bg termbox.Attribute, format string, args ...interface{}) {
+ s := fmt.Sprintf(format, args...)
+ print_tb(x, y, fg, bg, s)
+}
+
+func draw_key(k []key, fg, bg termbox.Attribute) {
+ for _, k := range k {
+ termbox.SetCell(k.x+2, k.y+4, k.ch, fg, bg)
+ }
+}
+
+func draw_keyboard() {
+ termbox.SetCell(0, 0, 0x250C, termbox.ColorWhite, termbox.ColorBlack)
+ termbox.SetCell(79, 0, 0x2510, termbox.ColorWhite, termbox.ColorBlack)
+ termbox.SetCell(0, 23, 0x2514, termbox.ColorWhite, termbox.ColorBlack)
+ termbox.SetCell(79, 23, 0x2518, termbox.ColorWhite, termbox.ColorBlack)
+
+ for i := 1; i < 79; i++ {
+ termbox.SetCell(i, 0, 0x2500, termbox.ColorWhite, termbox.ColorBlack)
+ termbox.SetCell(i, 23, 0x2500, termbox.ColorWhite, termbox.ColorBlack)
+ termbox.SetCell(i, 17, 0x2500, termbox.ColorWhite, termbox.ColorBlack)
+ termbox.SetCell(i, 4, 0x2500, termbox.ColorWhite, termbox.ColorBlack)
+ }
+ for i := 1; i < 23; i++ {
+ termbox.SetCell(0, i, 0x2502, termbox.ColorWhite, termbox.ColorBlack)
+ termbox.SetCell(79, i, 0x2502, termbox.ColorWhite, termbox.ColorBlack)
+ }
+ termbox.SetCell(0, 17, 0x251C, termbox.ColorWhite, termbox.ColorBlack)
+ termbox.SetCell(79, 17, 0x2524, termbox.ColorWhite, termbox.ColorBlack)
+ termbox.SetCell(0, 4, 0x251C, termbox.ColorWhite, termbox.ColorBlack)
+ termbox.SetCell(79, 4, 0x2524, termbox.ColorWhite, termbox.ColorBlack)
+ for i := 5; i < 17; i++ {
+ termbox.SetCell(1, i, 0x2588, termbox.ColorYellow, termbox.ColorYellow)
+ termbox.SetCell(78, i, 0x2588, termbox.ColorYellow, termbox.ColorYellow)
+ }
+
+ draw_key(K_ESC, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_F1, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_F2, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_F3, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_F4, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_F5, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_F6, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_F7, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_F8, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_F9, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_F10, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_F11, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_F12, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_PRN, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_SCR, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_BRK, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_LED1, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_LED2, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_LED3, termbox.ColorWhite, termbox.ColorBlue)
+
+ draw_key(K_TILDE, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_1, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_2, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_3, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_4, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_5, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_6, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_7, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_8, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_9, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_0, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_MINUS, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_EQUALS, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_BACKSLASH, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_BACKSPACE, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_INS, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_HOM, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_PGU, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_NUMLOCK, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_SLASH, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_STAR, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_MINUS, termbox.ColorWhite, termbox.ColorBlue)
+
+ draw_key(K_TAB, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_q, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_w, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_e, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_r, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_t, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_y, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_u, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_i, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_o, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_p, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_LSQB, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_RSQB, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_ENTER, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_DEL, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_END, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_PGD, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_7, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_8, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_9, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_PLUS, termbox.ColorWhite, termbox.ColorBlue)
+
+ draw_key(K_CAPS, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_a, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_s, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_d, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_f, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_g, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_h, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_j, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_k, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_l, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_SEMICOLON, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_QUOTE, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_4, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_5, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_6, termbox.ColorWhite, termbox.ColorBlue)
+
+ draw_key(K_LSHIFT, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_z, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_x, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_c, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_v, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_b, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_n, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_m, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_COMMA, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_PERIOD, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_SLASH, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_RSHIFT, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_ARROW_UP, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_1, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_2, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_3, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_ENTER, termbox.ColorWhite, termbox.ColorBlue)
+
+ draw_key(K_LCTRL, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_LWIN, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_LALT, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_SPACE, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_RCTRL, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_RPROP, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_RWIN, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_RALT, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_ARROW_LEFT, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_ARROW_DOWN, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_ARROW_RIGHT, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_0, termbox.ColorWhite, termbox.ColorBlue)
+ draw_key(K_K_PERIOD, termbox.ColorWhite, termbox.ColorBlue)
+
+ printf_tb(33, 1, termbox.ColorMagenta|termbox.AttrBold, termbox.ColorBlack, "Keyboard demo!")
+ printf_tb(21, 2, termbox.ColorMagenta, termbox.ColorBlack, "(press CTRL+X and then CTRL+Q to exit)")
+ printf_tb(15, 3, termbox.ColorMagenta, termbox.ColorBlack, "(press CTRL+X and then CTRL+C to change input mode)")
+
+ inputmode := termbox.SetInputMode(termbox.InputCurrent)
+ inputmode_str := ""
+ switch {
+ case inputmode&termbox.InputEsc != 0:
+ inputmode_str = "termbox.InputEsc"
+ case inputmode&termbox.InputAlt != 0:
+ inputmode_str = "termbox.InputAlt"
+ }
+
+ if inputmode&termbox.InputMouse != 0 {
+ inputmode_str += " | termbox.InputMouse"
+ }
+ printf_tb(3, 18, termbox.ColorWhite, termbox.ColorBlack, "Input mode: %s", inputmode_str)
+}
+
+var fcmap = []string{
+ "CTRL+2, CTRL+~",
+ "CTRL+A",
+ "CTRL+B",
+ "CTRL+C",
+ "CTRL+D",
+ "CTRL+E",
+ "CTRL+F",
+ "CTRL+G",
+ "CTRL+H, BACKSPACE",
+ "CTRL+I, TAB",
+ "CTRL+J",
+ "CTRL+K",
+ "CTRL+L",
+ "CTRL+M, ENTER",
+ "CTRL+N",
+ "CTRL+O",
+ "CTRL+P",
+ "CTRL+Q",
+ "CTRL+R",
+ "CTRL+S",
+ "CTRL+T",
+ "CTRL+U",
+ "CTRL+V",
+ "CTRL+W",
+ "CTRL+X",
+ "CTRL+Y",
+ "CTRL+Z",
+ "CTRL+3, ESC, CTRL+[",
+ "CTRL+4, CTRL+\\",
+ "CTRL+5, CTRL+]",
+ "CTRL+6",
+ "CTRL+7, CTRL+/, CTRL+_",
+ "SPACE",
+}
+
+var fkmap = []string{
+ "F1",
+ "F2",
+ "F3",
+ "F4",
+ "F5",
+ "F6",
+ "F7",
+ "F8",
+ "F9",
+ "F10",
+ "F11",
+ "F12",
+ "INSERT",
+ "DELETE",
+ "HOME",
+ "END",
+ "PGUP",
+ "PGDN",
+ "ARROW UP",
+ "ARROW DOWN",
+ "ARROW LEFT",
+ "ARROW RIGHT",
+}
+
+func funckeymap(k termbox.Key) string {
+ if k == termbox.KeyCtrl8 {
+ return "CTRL+8, BACKSPACE 2" /* 0x7F */
+ } else if k >= termbox.KeyArrowRight && k <= 0xFFFF {
+ return fkmap[0xFFFF-k]
+ } else if k <= termbox.KeySpace {
+ return fcmap[k]
+ }
+ return "UNKNOWN"
+}
+
+func pretty_print_press(ev *termbox.Event) {
+ printf_tb(3, 19, termbox.ColorWhite, termbox.ColorBlack, "Key: ")
+ printf_tb(8, 19, termbox.ColorYellow, termbox.ColorBlack, "decimal: %d", ev.Key)
+ printf_tb(8, 20, termbox.ColorGreen, termbox.ColorBlack, "hex: 0x%X", ev.Key)
+ printf_tb(8, 21, termbox.ColorCyan, termbox.ColorBlack, "octal: 0%o", ev.Key)
+ printf_tb(8, 22, termbox.ColorRed, termbox.ColorBlack, "string: %s", funckeymap(ev.Key))
+
+ printf_tb(54, 19, termbox.ColorWhite, termbox.ColorBlack, "Char: ")
+ printf_tb(60, 19, termbox.ColorYellow, termbox.ColorBlack, "decimal: %d", ev.Ch)
+ printf_tb(60, 20, termbox.ColorGreen, termbox.ColorBlack, "hex: 0x%X", ev.Ch)
+ printf_tb(60, 21, termbox.ColorCyan, termbox.ColorBlack, "octal: 0%o", ev.Ch)
+ printf_tb(60, 22, termbox.ColorRed, termbox.ColorBlack, "string: %s", string(ev.Ch))
+
+ modifier := "none"
+ if ev.Mod != 0 {
+ modifier = "termbox.ModAlt"
+ }
+ printf_tb(54, 18, termbox.ColorWhite, termbox.ColorBlack, "Modifier: %s", modifier)
+}
+
+func pretty_print_resize(ev *termbox.Event) {
+ printf_tb(3, 19, termbox.ColorWhite, termbox.ColorBlack, "Resize event: %d x %d", ev.Width, ev.Height)
+}
+
+var counter = 0
+
+func pretty_print_mouse(ev *termbox.Event) {
+ printf_tb(3, 19, termbox.ColorWhite, termbox.ColorBlack, "Mouse event: %d x %d", ev.MouseX, ev.MouseY)
+ button := ""
+ switch ev.Key {
+ case termbox.MouseLeft:
+ button = "MouseLeft: %d"
+ case termbox.MouseMiddle:
+ button = "MouseMiddle: %d"
+ case termbox.MouseRight:
+ button = "MouseRight: %d"
+ case termbox.MouseWheelUp:
+ button = "MouseWheelUp: %d"
+ case termbox.MouseWheelDown:
+ button = "MouseWheelDown: %d"
+ case termbox.MouseRelease:
+ button = "MouseRelease: %d"
+ }
+ if ev.Mod&termbox.ModMotion != 0 {
+ button += "*"
+ }
+ counter++
+ printf_tb(43, 19, termbox.ColorWhite, termbox.ColorBlack, "Key: ")
+ printf_tb(48, 19, termbox.ColorYellow, termbox.ColorBlack, button, counter)
+}
+
+func dispatch_press(ev *termbox.Event) {
+ if ev.Mod&termbox.ModAlt != 0 {
+ draw_key(K_LALT, termbox.ColorWhite, termbox.ColorRed)
+ draw_key(K_RALT, termbox.ColorWhite, termbox.ColorRed)
+ }
+
+ var k *combo
+ if ev.Key >= termbox.KeyArrowRight {
+ k = &func_combos[0xFFFF-ev.Key]
+ } else if ev.Ch < 128 {
+ if ev.Ch == 0 && ev.Key < 128 {
+ k = &combos[ev.Key]
+ } else {
+ k = &combos[ev.Ch]
+ }
+ }
+ if k == nil {
+ return
+ }
+
+ keys := k.keys
+ for _, k := range keys {
+ draw_key(k, termbox.ColorWhite, termbox.ColorRed)
+ }
+}
+
+func main() {
+ err := termbox.Init()
+ if err != nil {
+ panic(err)
+ }
+ defer termbox.Close()
+
+ termbox.SetInputMode(termbox.InputEsc | termbox.InputMouse)
+
+ termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
+ draw_keyboard()
+ termbox.Flush()
+ inputmode := 0
+ ctrlxpressed := false
+loop:
+ for {
+ switch ev := termbox.PollEvent(); ev.Type {
+ case termbox.EventKey:
+ if ev.Key == termbox.KeyCtrlS && ctrlxpressed {
+ termbox.Sync()
+ }
+ if ev.Key == termbox.KeyCtrlQ && ctrlxpressed {
+ break loop
+ }
+ if ev.Key == termbox.KeyCtrlC && ctrlxpressed {
+ chmap := []termbox.InputMode{
+ termbox.InputEsc | termbox.InputMouse,
+ termbox.InputAlt | termbox.InputMouse,
+ termbox.InputEsc,
+ termbox.InputAlt,
+ }
+ inputmode++
+ if inputmode >= len(chmap) {
+ inputmode = 0
+ }
+ termbox.SetInputMode(chmap[inputmode])
+ }
+ if ev.Key == termbox.KeyCtrlX {
+ ctrlxpressed = true
+ } else {
+ ctrlxpressed = false
+ }
+
+ termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
+ draw_keyboard()
+ dispatch_press(&ev)
+ pretty_print_press(&ev)
+ termbox.Flush()
+ case termbox.EventResize:
+ termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
+ draw_keyboard()
+ pretty_print_resize(&ev)
+ termbox.Flush()
+ case termbox.EventMouse:
+ termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
+ draw_keyboard()
+ pretty_print_mouse(&ev)
+ termbox.Flush()
+ case termbox.EventError:
+ panic(ev.Err)
+ }
+ }
+}
diff --git a/vendor/github.com/nsf/termbox-go/_demos/output.go b/vendor/github.com/nsf/termbox-go/_demos/output.go
new file mode 100644
index 0000000..2b9479b
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/_demos/output.go
@@ -0,0 +1,228 @@
+package main
+
+import "github.com/mattn/go-runewidth"
+import "github.com/nsf/termbox-go"
+
+const chars = "nnnnnnnnnbbbbbbbbbuuuuuuuuuBBBBBBBBB"
+
+var output_mode = termbox.OutputNormal
+
+func next_char(current int) int {
+ current++
+ if current >= len(chars) {
+ return 0
+ }
+ return current
+}
+
+func print_combinations_table(sx, sy int, attrs []termbox.Attribute) {
+ var bg termbox.Attribute
+ current_char := 0
+ y := sy
+
+ all_attrs := []termbox.Attribute{
+ 0,
+ termbox.AttrBold,
+ termbox.AttrUnderline,
+ termbox.AttrBold | termbox.AttrUnderline,
+ }
+
+ draw_line := func() {
+ x := sx
+ for _, a := range all_attrs {
+ for c := termbox.ColorDefault; c <= termbox.ColorWhite; c++ {
+ fg := a | c
+ termbox.SetCell(x, y, rune(chars[current_char]), fg, bg)
+ current_char = next_char(current_char)
+ x++
+ }
+ }
+ }
+
+ for _, a := range attrs {
+ for c := termbox.ColorDefault; c <= termbox.ColorWhite; c++ {
+ bg = a | c
+ draw_line()
+ y++
+ }
+ }
+}
+
+func print_wide(x, y int, s string) {
+ red := false
+ for _, r := range s {
+ c := termbox.ColorDefault
+ if red {
+ c = termbox.ColorRed
+ }
+ termbox.SetCell(x, y, r, termbox.ColorDefault, c)
+ w := runewidth.RuneWidth(r)
+ if w == 0 || (w == 2 && runewidth.IsAmbiguousWidth(r)) {
+ w = 1
+ }
+ x += w
+
+ red = !red
+ }
+}
+
+const hello_world = "こんにちは世界"
+
+func draw_all() {
+ termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
+
+ switch output_mode {
+
+ case termbox.OutputNormal:
+ print_combinations_table(1, 1, []termbox.Attribute{
+ 0,
+ termbox.AttrBold,
+ })
+ print_combinations_table(2+len(chars), 1, []termbox.Attribute{
+ termbox.AttrReverse,
+ })
+ print_wide(2+len(chars), 11, hello_world)
+
+ case termbox.OutputGrayscale:
+ for y := 0; y < 26; y++ {
+ for x := 0; x < 26; x++ {
+ termbox.SetCell(x, y, 'n',
+ termbox.Attribute(x+1),
+ termbox.Attribute(y+1))
+ termbox.SetCell(x+27, y, 'b',
+ termbox.Attribute(x+1)|termbox.AttrBold,
+ termbox.Attribute(26-y))
+ termbox.SetCell(x+54, y, 'u',
+ termbox.Attribute(x+1)|termbox.AttrUnderline,
+ termbox.Attribute(y+1))
+ }
+ termbox.SetCell(82, y, 'd',
+ termbox.Attribute(y+1),
+ termbox.ColorDefault)
+ termbox.SetCell(83, y, 'd',
+ termbox.ColorDefault,
+ termbox.Attribute(26-y))
+ }
+
+ case termbox.Output216:
+ for r := 0; r < 6; r++ {
+ for g := 0; g < 6; g++ {
+ for b := 0; b < 6; b++ {
+ y := r
+ x := g + 6*b
+ c1 := termbox.Attribute(1 + r*36 + g*6 + b)
+ bg := termbox.Attribute(1 + g*36 + b*6 + r)
+ c2 := termbox.Attribute(1 + b*36 + r*6 + g)
+ bc1 := c1 | termbox.AttrBold
+ uc1 := c1 | termbox.AttrUnderline
+ bc2 := c2 | termbox.AttrBold
+ uc2 := c2 | termbox.AttrUnderline
+ termbox.SetCell(x, y, 'n', c1, bg)
+ termbox.SetCell(x, y+6, 'b', bc1, bg)
+ termbox.SetCell(x, y+12, 'u', uc1, bg)
+ termbox.SetCell(x, y+18, 'B', bc1|uc1, bg)
+ termbox.SetCell(x+37, y, 'n', c2, bg)
+ termbox.SetCell(x+37, y+6, 'b', bc2, bg)
+ termbox.SetCell(x+37, y+12, 'u', uc2, bg)
+ termbox.SetCell(x+37, y+18, 'B', bc2|uc2, bg)
+ }
+ c1 := termbox.Attribute(1 + g*6 + r*36)
+ c2 := termbox.Attribute(6 + g*6 + r*36)
+ termbox.SetCell(74+g, r, 'd', c1, termbox.ColorDefault)
+ termbox.SetCell(74+g, r+6, 'd', c2, termbox.ColorDefault)
+ termbox.SetCell(74+g, r+12, 'd', termbox.ColorDefault, c1)
+ termbox.SetCell(74+g, r+18, 'd', termbox.ColorDefault, c2)
+ }
+ }
+
+ case termbox.Output256:
+ for y := 0; y < 4; y++ {
+ for x := 0; x < 8; x++ {
+ for z := 0; z < 8; z++ {
+ bg := termbox.Attribute(1 + y*64 + x*8 + z)
+ c1 := termbox.Attribute(256 - y*64 - x*8 - z)
+ c2 := termbox.Attribute(1 + y*64 + z*8 + x)
+ c3 := termbox.Attribute(256 - y*64 - z*8 - x)
+ c4 := termbox.Attribute(1 + y*64 + x*4 + z*4)
+ bold := c2 | termbox.AttrBold
+ under := c3 | termbox.AttrUnderline
+ both := c1 | termbox.AttrBold | termbox.AttrUnderline
+ termbox.SetCell(z+8*x, y, ' ', 0, bg)
+ termbox.SetCell(z+8*x, y+5, 'n', c4, bg)
+ termbox.SetCell(z+8*x, y+10, 'b', bold, bg)
+ termbox.SetCell(z+8*x, y+15, 'u', under, bg)
+ termbox.SetCell(z+8*x, y+20, 'B', both, bg)
+ }
+ }
+ }
+ for x := 0; x < 12; x++ {
+ for y := 0; y < 2; y++ {
+ c1 := termbox.Attribute(233 + y*12 + x)
+ termbox.SetCell(66+x, y, 'd', c1, termbox.ColorDefault)
+ termbox.SetCell(66+x, 2+y, 'd', termbox.ColorDefault, c1)
+ }
+ }
+ for x := 0; x < 6; x++ {
+ for y := 0; y < 6; y++ {
+ c1 := termbox.Attribute(17 + x*6 + y*36)
+ c2 := termbox.Attribute(17 + 5 + x*6 + y*36)
+ termbox.SetCell(66+x, 6+y, 'd', c1, termbox.ColorDefault)
+ termbox.SetCell(66+x, 12+y, 'd', c2, termbox.ColorDefault)
+ termbox.SetCell(72+x, 6+y, 'd', termbox.ColorDefault, c1)
+ termbox.SetCell(72+x, 12+y, 'd', termbox.ColorDefault, c2)
+ }
+ }
+
+ }
+
+ termbox.Flush()
+}
+
+var available_modes = []termbox.OutputMode{
+ termbox.OutputNormal,
+ termbox.OutputGrayscale,
+ termbox.Output216,
+ termbox.Output256,
+}
+
+var output_mode_index = 0
+
+func switch_output_mode(direction int) {
+ output_mode_index += direction
+ if output_mode_index < 0 {
+ output_mode_index = len(available_modes) - 1
+ } else if output_mode_index >= len(available_modes) {
+ output_mode_index = 0
+ }
+ output_mode = termbox.SetOutputMode(available_modes[output_mode_index])
+ termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
+ termbox.Sync()
+}
+
+func main() {
+ err := termbox.Init()
+ if err != nil {
+ panic(err)
+ }
+ defer termbox.Close()
+
+ draw_all()
+loop:
+ for {
+ switch ev := termbox.PollEvent(); ev.Type {
+ case termbox.EventKey:
+ switch ev.Key {
+ case termbox.KeyEsc:
+ break loop
+ case termbox.KeyArrowUp, termbox.KeyArrowRight:
+ switch_output_mode(1)
+ draw_all()
+ case termbox.KeyArrowDown, termbox.KeyArrowLeft:
+ switch_output_mode(-1)
+ draw_all()
+ }
+ case termbox.EventResize:
+ draw_all()
+ }
+ }
+}
diff --git a/vendor/github.com/nsf/termbox-go/_demos/paint.go b/vendor/github.com/nsf/termbox-go/_demos/paint.go
new file mode 100644
index 0000000..fbafd18
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/_demos/paint.go
@@ -0,0 +1,105 @@
+package main
+
+import (
+ "github.com/nsf/termbox-go"
+)
+
+var curCol = 0
+var curRune = 0
+var backbuf []termbox.Cell
+var bbw, bbh int
+
+var runes = []rune{' ', '░', '▒', '▓', '█'}
+var colors = []termbox.Attribute{
+ termbox.ColorBlack,
+ termbox.ColorRed,
+ termbox.ColorGreen,
+ termbox.ColorYellow,
+ termbox.ColorBlue,
+ termbox.ColorMagenta,
+ termbox.ColorCyan,
+ termbox.ColorWhite,
+}
+
+type attrFunc func(int) (rune, termbox.Attribute, termbox.Attribute)
+
+func updateAndDrawButtons(current *int, x, y int, mx, my int, n int, attrf attrFunc) {
+ lx, ly := x, y
+ for i := 0; i < n; i++ {
+ if lx <= mx && mx <= lx+3 && ly <= my && my <= ly+1 {
+ *current = i
+ }
+ r, fg, bg := attrf(i)
+ termbox.SetCell(lx+0, ly+0, r, fg, bg)
+ termbox.SetCell(lx+1, ly+0, r, fg, bg)
+ termbox.SetCell(lx+2, ly+0, r, fg, bg)
+ termbox.SetCell(lx+3, ly+0, r, fg, bg)
+ termbox.SetCell(lx+0, ly+1, r, fg, bg)
+ termbox.SetCell(lx+1, ly+1, r, fg, bg)
+ termbox.SetCell(lx+2, ly+1, r, fg, bg)
+ termbox.SetCell(lx+3, ly+1, r, fg, bg)
+ lx += 4
+ }
+ lx, ly = x, y
+ for i := 0; i < n; i++ {
+ if *current == i {
+ fg := termbox.ColorRed | termbox.AttrBold
+ bg := termbox.ColorDefault
+ termbox.SetCell(lx+0, ly+2, '^', fg, bg)
+ termbox.SetCell(lx+1, ly+2, '^', fg, bg)
+ termbox.SetCell(lx+2, ly+2, '^', fg, bg)
+ termbox.SetCell(lx+3, ly+2, '^', fg, bg)
+ }
+ lx += 4
+ }
+}
+
+func update_and_redraw_all(mx, my int) {
+ termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
+ if mx != -1 && my != -1 {
+ backbuf[bbw*my+mx] = termbox.Cell{Ch: runes[curRune], Fg: colors[curCol]}
+ }
+ copy(termbox.CellBuffer(), backbuf)
+ _, h := termbox.Size()
+ updateAndDrawButtons(&curRune, 0, 0, mx, my, len(runes), func(i int) (rune, termbox.Attribute, termbox.Attribute) {
+ return runes[i], termbox.ColorDefault, termbox.ColorDefault
+ })
+ updateAndDrawButtons(&curCol, 0, h-3, mx, my, len(colors), func(i int) (rune, termbox.Attribute, termbox.Attribute) {
+ return ' ', termbox.ColorDefault, colors[i]
+ })
+ termbox.Flush()
+}
+
+func reallocBackBuffer(w, h int) {
+ bbw, bbh = w, h
+ backbuf = make([]termbox.Cell, w*h)
+}
+
+func main() {
+ err := termbox.Init()
+ if err != nil {
+ panic(err)
+ }
+ defer termbox.Close()
+ termbox.SetInputMode(termbox.InputEsc | termbox.InputMouse)
+ reallocBackBuffer(termbox.Size())
+ update_and_redraw_all(-1, -1)
+
+mainloop:
+ for {
+ mx, my := -1, -1
+ switch ev := termbox.PollEvent(); ev.Type {
+ case termbox.EventKey:
+ if ev.Key == termbox.KeyEsc {
+ break mainloop
+ }
+ case termbox.EventMouse:
+ if ev.Key == termbox.MouseLeft {
+ mx, my = ev.MouseX, ev.MouseY
+ }
+ case termbox.EventResize:
+ reallocBackBuffer(ev.Width, ev.Height)
+ }
+ update_and_redraw_all(mx, my)
+ }
+}
diff --git a/vendor/github.com/nsf/termbox-go/_demos/random_output.go b/vendor/github.com/nsf/termbox-go/_demos/random_output.go
new file mode 100644
index 0000000..efcf0b7
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/_demos/random_output.go
@@ -0,0 +1,46 @@
+package main
+
+import "github.com/nsf/termbox-go"
+import "math/rand"
+import "time"
+
+func draw() {
+ w, h := termbox.Size()
+ termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
+ for y := 0; y < h; y++ {
+ for x := 0; x < w; x++ {
+ termbox.SetCell(x, y, ' ', termbox.ColorDefault,
+ termbox.Attribute(rand.Int()%8)+1)
+ }
+ }
+ termbox.Flush()
+}
+
+func main() {
+ err := termbox.Init()
+ if err != nil {
+ panic(err)
+ }
+ defer termbox.Close()
+
+ event_queue := make(chan termbox.Event)
+ go func() {
+ for {
+ event_queue <- termbox.PollEvent()
+ }
+ }()
+
+ draw()
+loop:
+ for {
+ select {
+ case ev := <-event_queue:
+ if ev.Type == termbox.EventKey && ev.Key == termbox.KeyEsc {
+ break loop
+ }
+ default:
+ draw()
+ time.Sleep(10 * time.Millisecond)
+ }
+ }
+}
diff --git a/vendor/github.com/nsf/termbox-go/_demos/raw_input.go b/vendor/github.com/nsf/termbox-go/_demos/raw_input.go
new file mode 100644
index 0000000..97a4897
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/_demos/raw_input.go
@@ -0,0 +1,109 @@
+package main
+
+import (
+ "fmt"
+ "github.com/nsf/termbox-go"
+ "strings"
+)
+
+func tbprint(x, y int, fg, bg termbox.Attribute, msg string) {
+ for _, c := range msg {
+ termbox.SetCell(x, y, c, fg, bg)
+ x++
+ }
+}
+
+var current string
+var curev termbox.Event
+
+func mouse_button_str(k termbox.Key) string {
+ switch k {
+ case termbox.MouseLeft:
+ return "MouseLeft"
+ case termbox.MouseMiddle:
+ return "MouseMiddle"
+ case termbox.MouseRight:
+ return "MouseRight"
+ case termbox.MouseRelease:
+ return "MouseRelease"
+ case termbox.MouseWheelUp:
+ return "MouseWheelUp"
+ case termbox.MouseWheelDown:
+ return "MouseWheelDown"
+ }
+ return "Key"
+}
+
+func mod_str(m termbox.Modifier) string {
+ var out []string
+ if m&termbox.ModAlt != 0 {
+ out = append(out, "ModAlt")
+ }
+ if m&termbox.ModMotion != 0 {
+ out = append(out, "ModMotion")
+ }
+ return strings.Join(out, " | ")
+}
+
+func redraw_all() {
+ const coldef = termbox.ColorDefault
+ termbox.Clear(coldef, coldef)
+ tbprint(0, 0, termbox.ColorMagenta, coldef, "Press 'q' to quit")
+ tbprint(0, 1, coldef, coldef, current)
+ switch curev.Type {
+ case termbox.EventKey:
+ tbprint(0, 2, coldef, coldef,
+ fmt.Sprintf("EventKey: k: %d, c: %c, mod: %s", curev.Key, curev.Ch, mod_str(curev.Mod)))
+ case termbox.EventMouse:
+ tbprint(0, 2, coldef, coldef,
+ fmt.Sprintf("EventMouse: x: %d, y: %d, b: %s, mod: %s",
+ curev.MouseX, curev.MouseY, mouse_button_str(curev.Key), mod_str(curev.Mod)))
+ case termbox.EventNone:
+ tbprint(0, 2, coldef, coldef, "EventNone")
+ }
+ tbprint(0, 3, coldef, coldef, fmt.Sprintf("%d", curev.N))
+ termbox.Flush()
+}
+
+func main() {
+ err := termbox.Init()
+ if err != nil {
+ panic(err)
+ }
+ defer termbox.Close()
+ termbox.SetInputMode(termbox.InputAlt | termbox.InputMouse)
+ redraw_all()
+
+ data := make([]byte, 0, 64)
+mainloop:
+ for {
+ if cap(data)-len(data) < 32 {
+ newdata := make([]byte, len(data), len(data)+32)
+ copy(newdata, data)
+ data = newdata
+ }
+ beg := len(data)
+ d := data[beg : beg+32]
+ switch ev := termbox.PollRawEvent(d); ev.Type {
+ case termbox.EventRaw:
+ data = data[:beg+ev.N]
+ current = fmt.Sprintf("%q", data)
+ if current == `"q"` {
+ break mainloop
+ }
+
+ for {
+ ev := termbox.ParseEvent(data)
+ if ev.N == 0 {
+ break
+ }
+ curev = ev
+ copy(data, data[curev.N:])
+ data = data[:len(data)-curev.N]
+ }
+ case termbox.EventError:
+ panic(ev.Err)
+ }
+ redraw_all()
+ }
+}
diff --git a/vendor/github.com/nsf/termbox-go/api.go b/vendor/github.com/nsf/termbox-go/api.go
new file mode 100644
index 0000000..d530ab5
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/api.go
@@ -0,0 +1,489 @@
+// +build !windows
+
+package termbox
+
+import "github.com/mattn/go-runewidth"
+import "fmt"
+import "os"
+import "os/signal"
+import "syscall"
+import "runtime"
+import "time"
+
+// public API
+
+// Initializes termbox library. This function should be called before any other functions.
+// After successful initialization, the library must be finalized using 'Close' function.
+//
+// Example usage:
+// err := termbox.Init()
+// if err != nil {
+// panic(err)
+// }
+// defer termbox.Close()
+func Init() error {
+ var err error
+
+ out, err = os.OpenFile("/dev/tty", syscall.O_WRONLY, 0)
+ if err != nil {
+ return err
+ }
+ in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
+ if err != nil {
+ return err
+ }
+
+ err = setup_term()
+ if err != nil {
+ return fmt.Errorf("termbox: error while reading terminfo data: %v", err)
+ }
+
+ signal.Notify(sigwinch, syscall.SIGWINCH)
+ signal.Notify(sigio, syscall.SIGIO)
+
+ _, err = fcntl(in, syscall.F_SETFL, syscall.O_ASYNC|syscall.O_NONBLOCK)
+ if err != nil {
+ return err
+ }
+ _, err = fcntl(in, syscall.F_SETOWN, syscall.Getpid())
+ if runtime.GOOS != "darwin" && err != nil {
+ return err
+ }
+ err = tcgetattr(out.Fd(), &orig_tios)
+ if err != nil {
+ return err
+ }
+
+ tios := orig_tios
+ tios.Iflag &^= syscall_IGNBRK | syscall_BRKINT | syscall_PARMRK |
+ syscall_ISTRIP | syscall_INLCR | syscall_IGNCR |
+ syscall_ICRNL | syscall_IXON
+ tios.Lflag &^= syscall_ECHO | syscall_ECHONL | syscall_ICANON |
+ syscall_ISIG | syscall_IEXTEN
+ tios.Cflag &^= syscall_CSIZE | syscall_PARENB
+ tios.Cflag |= syscall_CS8
+ tios.Cc[syscall_VMIN] = 1
+ tios.Cc[syscall_VTIME] = 0
+
+ err = tcsetattr(out.Fd(), &tios)
+ if err != nil {
+ return err
+ }
+
+ out.WriteString(funcs[t_enter_ca])
+ out.WriteString(funcs[t_enter_keypad])
+ out.WriteString(funcs[t_hide_cursor])
+ out.WriteString(funcs[t_clear_screen])
+
+ termw, termh = get_term_size(out.Fd())
+ back_buffer.init(termw, termh)
+ front_buffer.init(termw, termh)
+ back_buffer.clear()
+ front_buffer.clear()
+
+ go func() {
+ buf := make([]byte, 128)
+ for {
+ select {
+ case <-sigio:
+ for {
+ n, err := syscall.Read(in, buf)
+ if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK {
+ break
+ }
+ select {
+ case input_comm <- input_event{buf[:n], err}:
+ ie := <-input_comm
+ buf = ie.data[:128]
+ case <-quit:
+ return
+ }
+ }
+ case <-quit:
+ return
+ }
+ }
+ }()
+
+ IsInit = true
+ return nil
+}
+
+// Interrupt an in-progress call to PollEvent by causing it to return
+// EventInterrupt. Note that this function will block until the PollEvent
+// function has successfully been interrupted.
+func Interrupt() {
+ interrupt_comm <- struct{}{}
+}
+
+// Finalizes termbox library, should be called after successful initialization
+// when termbox's functionality isn't required anymore.
+func Close() {
+ quit <- 1
+ out.WriteString(funcs[t_show_cursor])
+ out.WriteString(funcs[t_sgr0])
+ out.WriteString(funcs[t_clear_screen])
+ out.WriteString(funcs[t_exit_ca])
+ out.WriteString(funcs[t_exit_keypad])
+ out.WriteString(funcs[t_exit_mouse])
+ tcsetattr(out.Fd(), &orig_tios)
+
+ out.Close()
+ syscall.Close(in)
+
+ // reset the state, so that on next Init() it will work again
+ termw = 0
+ termh = 0
+ input_mode = InputEsc
+ out = nil
+ in = 0
+ lastfg = attr_invalid
+ lastbg = attr_invalid
+ lastx = coord_invalid
+ lasty = coord_invalid
+ cursor_x = cursor_hidden
+ cursor_y = cursor_hidden
+ foreground = ColorDefault
+ background = ColorDefault
+ IsInit = false
+}
+
+// Synchronizes the internal back buffer with the terminal.
+func Flush() error {
+ // invalidate cursor position
+ lastx = coord_invalid
+ lasty = coord_invalid
+
+ update_size_maybe()
+
+ for y := 0; y < front_buffer.height; y++ {
+ line_offset := y * front_buffer.width
+ for x := 0; x < front_buffer.width; {
+ cell_offset := line_offset + x
+ back := &back_buffer.cells[cell_offset]
+ front := &front_buffer.cells[cell_offset]
+ if back.Ch < ' ' {
+ back.Ch = ' '
+ }
+ w := runewidth.RuneWidth(back.Ch)
+ if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(back.Ch) {
+ w = 1
+ }
+ if *back == *front {
+ x += w
+ continue
+ }
+ *front = *back
+ send_attr(back.Fg, back.Bg)
+
+ if w == 2 && x == front_buffer.width-1 {
+ // there's not enough space for 2-cells rune,
+ // let's just put a space in there
+ send_char(x, y, ' ')
+ } else {
+ send_char(x, y, back.Ch)
+ if w == 2 {
+ next := cell_offset + 1
+ front_buffer.cells[next] = Cell{
+ Ch: 0,
+ Fg: back.Fg,
+ Bg: back.Bg,
+ }
+ }
+ }
+ x += w
+ }
+ }
+ if !is_cursor_hidden(cursor_x, cursor_y) {
+ write_cursor(cursor_x, cursor_y)
+ }
+ return flush()
+}
+
+// Sets the position of the cursor. See also HideCursor().
+func SetCursor(x, y int) {
+ if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) {
+ outbuf.WriteString(funcs[t_show_cursor])
+ }
+
+ if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) {
+ outbuf.WriteString(funcs[t_hide_cursor])
+ }
+
+ cursor_x, cursor_y = x, y
+ if !is_cursor_hidden(cursor_x, cursor_y) {
+ write_cursor(cursor_x, cursor_y)
+ }
+}
+
+// The shortcut for SetCursor(-1, -1).
+func HideCursor() {
+ SetCursor(cursor_hidden, cursor_hidden)
+}
+
+// Changes cell's parameters in the internal back buffer at the specified
+// position.
+func SetCell(x, y int, ch rune, fg, bg Attribute) {
+ if x < 0 || x >= back_buffer.width {
+ return
+ }
+ if y < 0 || y >= back_buffer.height {
+ return
+ }
+
+ back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg}
+}
+
+// Returns a slice into the termbox's back buffer. You can get its dimensions
+// using 'Size' function. The slice remains valid as long as no 'Clear' or
+// 'Flush' function calls were made after call to this function.
+func CellBuffer() []Cell {
+ return back_buffer.cells
+}
+
+// After getting a raw event from PollRawEvent function call, you can parse it
+// again into an ordinary one using termbox logic. That is parse an event as
+// termbox would do it. Returned event in addition to usual Event struct fields
+// sets N field to the amount of bytes used within 'data' slice. If the length
+// of 'data' slice is zero or event cannot be parsed for some other reason, the
+// function will return a special event type: EventNone.
+//
+// IMPORTANT: EventNone may contain a non-zero N, which means you should skip
+// these bytes, because termbox cannot recognize them.
+//
+// NOTE: This API is experimental and may change in future.
+func ParseEvent(data []byte) Event {
+ event := Event{Type: EventKey}
+ status := extract_event(data, &event, false)
+ if status != event_extracted {
+ return Event{Type: EventNone, N: event.N}
+ }
+ return event
+}
+
+// Wait for an event and return it. This is a blocking function call. Instead
+// of EventKey and EventMouse it returns EventRaw events. Raw event is written
+// into `data` slice and Event's N field is set to the amount of bytes written.
+// The minimum required length of the 'data' slice is 1. This requirement may
+// vary on different platforms.
+//
+// NOTE: This API is experimental and may change in future.
+func PollRawEvent(data []byte) Event {
+ if len(data) == 0 {
+ panic("len(data) >= 1 is a requirement")
+ }
+
+ var event Event
+ if extract_raw_event(data, &event) {
+ return event
+ }
+
+ for {
+ select {
+ case ev := <-input_comm:
+ if ev.err != nil {
+ return Event{Type: EventError, Err: ev.err}
+ }
+
+ inbuf = append(inbuf, ev.data...)
+ input_comm <- ev
+ if extract_raw_event(data, &event) {
+ return event
+ }
+ case <-interrupt_comm:
+ event.Type = EventInterrupt
+ return event
+
+ case <-sigwinch:
+ event.Type = EventResize
+ event.Width, event.Height = get_term_size(out.Fd())
+ return event
+ }
+ }
+}
+
+// Wait for an event and return it. This is a blocking function call.
+func PollEvent() Event {
+ // Constant governing macOS specific behavior. See https://github.com/nsf/termbox-go/issues/132
+ // This is an arbitrary delay which hopefully will be enough time for any lagging
+ // partial escape sequences to come through.
+ const esc_wait_delay = 100 * time.Millisecond
+
+ var event Event
+ var esc_wait_timer *time.Timer
+ var esc_timeout <-chan time.Time
+
+ // try to extract event from input buffer, return on success
+ event.Type = EventKey
+ status := extract_event(inbuf, &event, true)
+ if event.N != 0 {
+ copy(inbuf, inbuf[event.N:])
+ inbuf = inbuf[:len(inbuf)-event.N]
+ }
+ if status == event_extracted {
+ return event
+ } else if status == esc_wait {
+ esc_wait_timer = time.NewTimer(esc_wait_delay)
+ esc_timeout = esc_wait_timer.C
+ }
+
+ for {
+ select {
+ case ev := <-input_comm:
+ if esc_wait_timer != nil {
+ if !esc_wait_timer.Stop() {
+ <-esc_wait_timer.C
+ }
+ esc_wait_timer = nil
+ }
+
+ if ev.err != nil {
+ return Event{Type: EventError, Err: ev.err}
+ }
+
+ inbuf = append(inbuf, ev.data...)
+ input_comm <- ev
+ status := extract_event(inbuf, &event, true)
+ if event.N != 0 {
+ copy(inbuf, inbuf[event.N:])
+ inbuf = inbuf[:len(inbuf)-event.N]
+ }
+ if status == event_extracted {
+ return event
+ } else if status == esc_wait {
+ esc_wait_timer = time.NewTimer(esc_wait_delay)
+ esc_timeout = esc_wait_timer.C
+ }
+ case <-esc_timeout:
+ esc_wait_timer = nil
+
+ status := extract_event(inbuf, &event, false)
+ if event.N != 0 {
+ copy(inbuf, inbuf[event.N:])
+ inbuf = inbuf[:len(inbuf)-event.N]
+ }
+ if status == event_extracted {
+ return event
+ }
+ case <-interrupt_comm:
+ event.Type = EventInterrupt
+ return event
+
+ case <-sigwinch:
+ event.Type = EventResize
+ event.Width, event.Height = get_term_size(out.Fd())
+ return event
+ }
+ }
+}
+
+// Returns the size of the internal back buffer (which is mostly the same as
+// terminal's window size in characters). But it doesn't always match the size
+// of the terminal window, after the terminal size has changed, the internal
+// back buffer will get in sync only after Clear or Flush function calls.
+func Size() (width int, height int) {
+ return termw, termh
+}
+
+// Clears the internal back buffer.
+func Clear(fg, bg Attribute) error {
+ foreground, background = fg, bg
+ err := update_size_maybe()
+ back_buffer.clear()
+ return err
+}
+
+// Sets termbox input mode. Termbox has two input modes:
+//
+// 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match
+// any known sequence. ESC means KeyEsc. This is the default input mode.
+//
+// 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match
+// any known sequence. ESC enables ModAlt modifier for the next keyboard event.
+//
+// Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will
+// enable mouse button press/release and drag events.
+//
+// If 'mode' is InputCurrent, returns the current input mode. See also Input*
+// constants.
+func SetInputMode(mode InputMode) InputMode {
+ if mode == InputCurrent {
+ return input_mode
+ }
+ if mode&(InputEsc|InputAlt) == 0 {
+ mode |= InputEsc
+ }
+ if mode&(InputEsc|InputAlt) == InputEsc|InputAlt {
+ mode &^= InputAlt
+ }
+ if mode&InputMouse != 0 {
+ out.WriteString(funcs[t_enter_mouse])
+ } else {
+ out.WriteString(funcs[t_exit_mouse])
+ }
+
+ input_mode = mode
+ return input_mode
+}
+
+// Sets the termbox output mode. Termbox has four output options:
+//
+// 1. OutputNormal => [1..8]
+// This mode provides 8 different colors:
+// black, red, green, yellow, blue, magenta, cyan, white
+// Shortcut: ColorBlack, ColorRed, ...
+// Attributes: AttrBold, AttrUnderline, AttrReverse
+//
+// Example usage:
+// SetCell(x, y, '@', ColorBlack | AttrBold, ColorRed);
+//
+// 2. Output256 => [1..256]
+// In this mode you can leverage the 256 terminal mode:
+// 0x01 - 0x08: the 8 colors as in OutputNormal
+// 0x09 - 0x10: Color* | AttrBold
+// 0x11 - 0xe8: 216 different colors
+// 0xe9 - 0x1ff: 24 different shades of grey
+//
+// Example usage:
+// SetCell(x, y, '@', 184, 240);
+// SetCell(x, y, '@', 0xb8, 0xf0);
+//
+// 3. Output216 => [1..216]
+// This mode supports the 3rd range of the 256 mode only.
+// But you don't need to provide an offset.
+//
+// 4. OutputGrayscale => [1..26]
+// This mode supports the 4th range of the 256 mode
+// and black and white colors from 3th range of the 256 mode
+// But you don't need to provide an offset.
+//
+// In all modes, 0x00 represents the default color.
+//
+// `go run _demos/output.go` to see its impact on your terminal.
+//
+// If 'mode' is OutputCurrent, it returns the current output mode.
+//
+// Note that this may return a different OutputMode than the one requested,
+// as the requested mode may not be available on the target platform.
+func SetOutputMode(mode OutputMode) OutputMode {
+ if mode == OutputCurrent {
+ return output_mode
+ }
+
+ output_mode = mode
+ return output_mode
+}
+
+// Sync comes handy when something causes desync between termbox's understanding
+// of a terminal buffer and the reality. Such as a third party process. Sync
+// forces a complete resync between the termbox and a terminal, it may not be
+// visually pretty though.
+func Sync() error {
+ front_buffer.clear()
+ err := send_clear()
+ if err != nil {
+ return err
+ }
+
+ return Flush()
+}
diff --git a/vendor/github.com/nsf/termbox-go/api_common.go b/vendor/github.com/nsf/termbox-go/api_common.go
new file mode 100644
index 0000000..5ca1371
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/api_common.go
@@ -0,0 +1,187 @@
+// termbox is a library for creating cross-platform text-based interfaces
+package termbox
+
+// public API, common OS agnostic part
+
+type (
+ InputMode int
+ OutputMode int
+ EventType uint8
+ Modifier uint8
+ Key uint16
+ Attribute uint16
+)
+
+// This type represents a termbox event. The 'Mod', 'Key' and 'Ch' fields are
+// valid if 'Type' is EventKey. The 'Width' and 'Height' fields are valid if
+// 'Type' is EventResize. The 'Err' field is valid if 'Type' is EventError.
+type Event struct {
+ Type EventType // one of Event* constants
+ Mod Modifier // one of Mod* constants or 0
+ Key Key // one of Key* constants, invalid if 'Ch' is not 0
+ Ch rune // a unicode character
+ Width int // width of the screen
+ Height int // height of the screen
+ Err error // error in case if input failed
+ MouseX int // x coord of mouse
+ MouseY int // y coord of mouse
+ N int // number of bytes written when getting a raw event
+}
+
+// A cell, single conceptual entity on the screen. The screen is basically a 2d
+// array of cells. 'Ch' is a unicode character, 'Fg' and 'Bg' are foreground
+// and background attributes respectively.
+type Cell struct {
+ Ch rune
+ Fg Attribute
+ Bg Attribute
+}
+
+// To know if termbox has been initialized or not
+var (
+ IsInit bool = false
+)
+
+// Key constants, see Event.Key field.
+const (
+ KeyF1 Key = 0xFFFF - iota
+ KeyF2
+ KeyF3
+ KeyF4
+ KeyF5
+ KeyF6
+ KeyF7
+ KeyF8
+ KeyF9
+ KeyF10
+ KeyF11
+ KeyF12
+ KeyInsert
+ KeyDelete
+ KeyHome
+ KeyEnd
+ KeyPgup
+ KeyPgdn
+ KeyArrowUp
+ KeyArrowDown
+ KeyArrowLeft
+ KeyArrowRight
+ key_min // see terminfo
+ MouseLeft
+ MouseMiddle
+ MouseRight
+ MouseRelease
+ MouseWheelUp
+ MouseWheelDown
+)
+
+const (
+ KeyCtrlTilde Key = 0x00
+ KeyCtrl2 Key = 0x00
+ KeyCtrlSpace Key = 0x00
+ KeyCtrlA Key = 0x01
+ KeyCtrlB Key = 0x02
+ KeyCtrlC Key = 0x03
+ KeyCtrlD Key = 0x04
+ KeyCtrlE Key = 0x05
+ KeyCtrlF Key = 0x06
+ KeyCtrlG Key = 0x07
+ KeyBackspace Key = 0x08
+ KeyCtrlH Key = 0x08
+ KeyTab Key = 0x09
+ KeyCtrlI Key = 0x09
+ KeyCtrlJ Key = 0x0A
+ KeyCtrlK Key = 0x0B
+ KeyCtrlL Key = 0x0C
+ KeyEnter Key = 0x0D
+ KeyCtrlM Key = 0x0D
+ KeyCtrlN Key = 0x0E
+ KeyCtrlO Key = 0x0F
+ KeyCtrlP Key = 0x10
+ KeyCtrlQ Key = 0x11
+ KeyCtrlR Key = 0x12
+ KeyCtrlS Key = 0x13
+ KeyCtrlT Key = 0x14
+ KeyCtrlU Key = 0x15
+ KeyCtrlV Key = 0x16
+ KeyCtrlW Key = 0x17
+ KeyCtrlX Key = 0x18
+ KeyCtrlY Key = 0x19
+ KeyCtrlZ Key = 0x1A
+ KeyEsc Key = 0x1B
+ KeyCtrlLsqBracket Key = 0x1B
+ KeyCtrl3 Key = 0x1B
+ KeyCtrl4 Key = 0x1C
+ KeyCtrlBackslash Key = 0x1C
+ KeyCtrl5 Key = 0x1D
+ KeyCtrlRsqBracket Key = 0x1D
+ KeyCtrl6 Key = 0x1E
+ KeyCtrl7 Key = 0x1F
+ KeyCtrlSlash Key = 0x1F
+ KeyCtrlUnderscore Key = 0x1F
+ KeySpace Key = 0x20
+ KeyBackspace2 Key = 0x7F
+ KeyCtrl8 Key = 0x7F
+)
+
+// Alt modifier constant, see Event.Mod field and SetInputMode function.
+const (
+ ModAlt Modifier = 1 << iota
+ ModMotion
+)
+
+// Cell colors, you can combine a color with multiple attributes using bitwise
+// OR ('|').
+const (
+ ColorDefault Attribute = iota
+ ColorBlack
+ ColorRed
+ ColorGreen
+ ColorYellow
+ ColorBlue
+ ColorMagenta
+ ColorCyan
+ ColorWhite
+)
+
+// Cell attributes, it is possible to use multiple attributes by combining them
+// using bitwise OR ('|'). Although, colors cannot be combined. But you can
+// combine attributes and a single color.
+//
+// It's worth mentioning that some platforms don't support certain attributes.
+// For example windows console doesn't support AttrUnderline. And on some
+// terminals applying AttrBold to background may result in blinking text. Use
+// them with caution and test your code on various terminals.
+const (
+ AttrBold Attribute = 1 << (iota + 9)
+ AttrUnderline
+ AttrReverse
+)
+
+// Input mode. See SetInputMode function.
+const (
+ InputEsc InputMode = 1 << iota
+ InputAlt
+ InputMouse
+ InputCurrent InputMode = 0
+)
+
+// Output mode. See SetOutputMode function.
+const (
+ OutputCurrent OutputMode = iota
+ OutputNormal
+ Output256
+ Output216
+ OutputGrayscale
+)
+
+// Event type. See Event.Type field.
+const (
+ EventKey EventType = iota
+ EventResize
+ EventMouse
+ EventError
+ EventInterrupt
+ EventRaw
+ EventNone
+)
diff --git a/vendor/github.com/nsf/termbox-go/api_windows.go b/vendor/github.com/nsf/termbox-go/api_windows.go
new file mode 100644
index 0000000..7def30a
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/api_windows.go
@@ -0,0 +1,239 @@
+package termbox
+
+import (
+ "syscall"
+)
+
+// public API
+
+// Initializes termbox library. This function should be called before any other functions.
+// After successful initialization, the library must be finalized using 'Close' function.
+//
+// Example usage:
+// err := termbox.Init()
+// if err != nil {
+// panic(err)
+// }
+// defer termbox.Close()
+func Init() error {
+ var err error
+
+ interrupt, err = create_event()
+ if err != nil {
+ return err
+ }
+
+ in, err = syscall.Open("CONIN$", syscall.O_RDWR, 0)
+ if err != nil {
+ return err
+ }
+ out, err = syscall.Open("CONOUT$", syscall.O_RDWR, 0)
+ if err != nil {
+ return err
+ }
+
+ err = get_console_mode(in, &orig_mode)
+ if err != nil {
+ return err
+ }
+
+ err = set_console_mode(in, enable_window_input)
+ if err != nil {
+ return err
+ }
+
+ orig_size = get_term_size(out)
+ win_size := get_win_size(out)
+
+ err = set_console_screen_buffer_size(out, win_size)
+ if err != nil {
+ return err
+ }
+
+ err = get_console_cursor_info(out, &orig_cursor_info)
+ if err != nil {
+ return err
+ }
+
+ show_cursor(false)
+ term_size = get_term_size(out)
+ back_buffer.init(int(term_size.x), int(term_size.y))
+ front_buffer.init(int(term_size.x), int(term_size.y))
+ back_buffer.clear()
+ front_buffer.clear()
+ clear()
+
+ diffbuf = make([]diff_msg, 0, 32)
+
+ go input_event_producer()
+ IsInit = true
+ return nil
+}
+
+// Finalizes termbox library, should be called after successful initialization
+// when termbox's functionality isn't required anymore.
+func Close() {
+ // we ignore errors here, because we can't really do anything about them
+ Clear(0, 0)
+ Flush()
+
+ // stop event producer
+ cancel_comm <- true
+ set_event(interrupt)
+ select {
+ case <-input_comm:
+ default:
+ }
+ <-cancel_done_comm
+
+ set_console_cursor_info(out, &orig_cursor_info)
+ set_console_cursor_position(out, coord{})
+ set_console_screen_buffer_size(out, orig_size)
+ set_console_mode(in, orig_mode)
+ syscall.Close(in)
+ syscall.Close(out)
+ syscall.Close(interrupt)
+ IsInit = false
+}
+
+// Interrupt an in-progress call to PollEvent by causing it to return
+// EventInterrupt. Note that this function will block until the PollEvent
+// function has successfully been interrupted.
+func Interrupt() {
+ interrupt_comm <- struct{}{}
+}
+
+// Synchronizes the internal back buffer with the terminal.
+func Flush() error {
+ update_size_maybe()
+ prepare_diff_messages()
+ for _, diff := range diffbuf {
+ r := small_rect{
+ left: 0,
+ top: diff.pos,
+ right: term_size.x - 1,
+ bottom: diff.pos + diff.lines - 1,
+ }
+ write_console_output(out, diff.chars, r)
+ }
+ if !is_cursor_hidden(cursor_x, cursor_y) {
+ move_cursor(cursor_x, cursor_y)
+ }
+ return nil
+}
+
+// Sets the position of the cursor. See also HideCursor().
+func SetCursor(x, y int) {
+ if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) {
+ show_cursor(true)
+ }
+
+ if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) {
+ show_cursor(false)
+ }
+
+ cursor_x, cursor_y = x, y
+ if !is_cursor_hidden(cursor_x, cursor_y) {
+ move_cursor(cursor_x, cursor_y)
+ }
+}
+
+// The shortcut for SetCursor(-1, -1).
+func HideCursor() {
+ SetCursor(cursor_hidden, cursor_hidden)
+}
+
+// Changes cell's parameters in the internal back buffer at the specified
+// position.
+func SetCell(x, y int, ch rune, fg, bg Attribute) {
+ if x < 0 || x >= back_buffer.width {
+ return
+ }
+ if y < 0 || y >= back_buffer.height {
+ return
+ }
+
+ back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg}
+}
+
+// Returns a slice into the termbox's back buffer. You can get its dimensions
+// using 'Size' function. The slice remains valid as long as no 'Clear' or
+// 'Flush' function calls were made after call to this function.
+func CellBuffer() []Cell {
+ return back_buffer.cells
+}
+
+// Wait for an event and return it. This is a blocking function call.
+func PollEvent() Event {
+ select {
+ case ev := <-input_comm:
+ return ev
+ case <-interrupt_comm:
+ return Event{Type: EventInterrupt}
+ }
+}
+
+// Returns the size of the internal back buffer (which is mostly the same as
+// console's window size in characters). But it doesn't always match the size
+// of the console window, after the console size has changed, the internal back
+// buffer will get in sync only after Clear or Flush function calls.
+func Size() (int, int) {
+ return int(term_size.x), int(term_size.y)
+}
+
+// Clears the internal back buffer.
+func Clear(fg, bg Attribute) error {
+ foreground, background = fg, bg
+ update_size_maybe()
+ back_buffer.clear()
+ return nil
+}
+
+// Sets termbox input mode. Termbox has two input modes:
+//
+// 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match
+// any known sequence. ESC means KeyEsc. This is the default input mode.
+//
+// 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match
+// any known sequence. ESC enables ModAlt modifier for the next keyboard event.
+//
+// Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will
+// enable mouse button press/release and drag events.
+//
+// If 'mode' is InputCurrent, returns the current input mode. See also Input*
+// constants.
+func SetInputMode(mode InputMode) InputMode {
+ if mode == InputCurrent {
+ return input_mode
+ }
+ if mode&InputMouse != 0 {
+ err := set_console_mode(in, enable_window_input|enable_mouse_input|enable_extended_flags)
+ if err != nil {
+ panic(err)
+ }
+ } else {
+ err := set_console_mode(in, enable_window_input)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ input_mode = mode
+ return input_mode
+}
+
+// Sets the termbox output mode.
+//
+// Windows console does not support extra colour modes,
+// so this will always set and return OutputNormal.
+func SetOutputMode(mode OutputMode) OutputMode {
+ return OutputNormal
+}
+
+// Sync comes handy when something causes desync between termbox's understanding
+// of a terminal buffer and the reality. Such as a third party process. Sync
+// forces a complete resync between the termbox and a terminal, it may not be
+// visually pretty though. At the moment on Windows it does nothing.
+func Sync() error {
+ return nil
+}
diff --git a/vendor/github.com/nsf/termbox-go/collect_terminfo.py b/vendor/github.com/nsf/termbox-go/collect_terminfo.py
new file mode 100755
index 0000000..5e50975
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/collect_terminfo.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+
+import sys, os, subprocess
+
+def escaped(s):
+ return repr(s)[1:-1]
+
+def tput(term, name):
+ try:
+ return subprocess.check_output(['tput', '-T%s' % term, name]).decode()
+ except subprocess.CalledProcessError as e:
+ return e.output.decode()
+
+
+def w(s):
+ if s == None:
+ return
+ sys.stdout.write(s)
+
+terminals = {
+ 'xterm' : 'xterm',
+ 'rxvt-256color' : 'rxvt_256color',
+ 'rxvt-unicode' : 'rxvt_unicode',
+ 'linux' : 'linux',
+ 'Eterm' : 'eterm',
+ 'screen' : 'screen'
+}
+
+keys = [
+ "F1", "kf1",
+ "F2", "kf2",
+ "F3", "kf3",
+ "F4", "kf4",
+ "F5", "kf5",
+ "F6", "kf6",
+ "F7", "kf7",
+ "F8", "kf8",
+ "F9", "kf9",
+ "F10", "kf10",
+ "F11", "kf11",
+ "F12", "kf12",
+ "INSERT", "kich1",
+ "DELETE", "kdch1",
+ "HOME", "khome",
+ "END", "kend",
+ "PGUP", "kpp",
+ "PGDN", "knp",
+ "KEY_UP", "kcuu1",
+ "KEY_DOWN", "kcud1",
+ "KEY_LEFT", "kcub1",
+ "KEY_RIGHT", "kcuf1"
+]
+
+funcs = [
+ "T_ENTER_CA", "smcup",
+ "T_EXIT_CA", "rmcup",
+ "T_SHOW_CURSOR", "cnorm",
+ "T_HIDE_CURSOR", "civis",
+ "T_CLEAR_SCREEN", "clear",
+ "T_SGR0", "sgr0",
+ "T_UNDERLINE", "smul",
+ "T_BOLD", "bold",
+ "T_BLINK", "blink",
+ "T_REVERSE", "rev",
+ "T_ENTER_KEYPAD", "smkx",
+ "T_EXIT_KEYPAD", "rmkx"
+]
+
+def iter_pairs(iterable):
+ iterable = iter(iterable)
+ while True:
+ yield (next(iterable), next(iterable))
+
+def do_term(term, nick):
+ w("// %s\n" % term)
+ w("var %s_keys = []string{\n\t" % nick)
+ for k, v in iter_pairs(keys):
+ w('"')
+ w(escaped(tput(term, v)))
+ w('",')
+ w("\n}\n")
+ w("var %s_funcs = []string{\n\t" % nick)
+ for k,v in iter_pairs(funcs):
+ w('"')
+ if v == "sgr":
+ w("\\033[3%d;4%dm")
+ elif v == "cup":
+ w("\\033[%d;%dH")
+ else:
+ w(escaped(tput(term, v)))
+ w('", ')
+ w("\n}\n\n")
+
+def do_terms(d):
+ w("var terms = []struct {\n")
+ w("\tname string\n")
+ w("\tkeys []string\n")
+ w("\tfuncs []string\n")
+ w("}{\n")
+ for k, v in d.items():
+ w('\t{"%s", %s_keys, %s_funcs},\n' % (k, v, v))
+ w("}\n\n")
+
+w("// +build !windows\n\npackage termbox\n\n")
+
+for k,v in terminals.items():
+ do_term(k, v)
+
+do_terms(terminals)
+
diff --git a/vendor/github.com/nsf/termbox-go/escwait.go b/vendor/github.com/nsf/termbox-go/escwait.go
new file mode 100644
index 0000000..b7bbb89
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/escwait.go
@@ -0,0 +1,11 @@
+// +build !darwin
+
+package termbox
+
+// On all systems other than macOS, disable behavior which will wait before
+// deciding that the escape key was pressed, to account for partially send
+// escape sequences, especially with regard to lengthy mouse sequences.
+// See https://github.com/nsf/termbox-go/issues/132
+func enable_wait_for_escape_sequence() bool {
+ return false
+}
diff --git a/vendor/github.com/nsf/termbox-go/escwait_darwin.go b/vendor/github.com/nsf/termbox-go/escwait_darwin.go
new file mode 100644
index 0000000..dde69b6
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/escwait_darwin.go
@@ -0,0 +1,9 @@
+package termbox
+
+// On macOS, enable behavior which will wait before deciding that the escape
+// key was pressed, to account for partially send escape sequences, especially
+// with regard to lengthy mouse sequences.
+// See https://github.com/nsf/termbox-go/issues/132
+func enable_wait_for_escape_sequence() bool {
+ return true
+}
diff --git a/vendor/github.com/nsf/termbox-go/syscalls.go b/vendor/github.com/nsf/termbox-go/syscalls.go
new file mode 100644
index 0000000..4f52bb9
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/syscalls.go
@@ -0,0 +1,39 @@
+// +build ignore
+
+package termbox
+
+/*
+#include
+#include
+*/
+import "C"
+
+type syscall_Termios C.struct_termios
+
+const (
+ syscall_IGNBRK = C.IGNBRK
+ syscall_BRKINT = C.BRKINT
+ syscall_PARMRK = C.PARMRK
+ syscall_ISTRIP = C.ISTRIP
+ syscall_INLCR = C.INLCR
+ syscall_IGNCR = C.IGNCR
+ syscall_ICRNL = C.ICRNL
+ syscall_IXON = C.IXON
+ syscall_OPOST = C.OPOST
+ syscall_ECHO = C.ECHO
+ syscall_ECHONL = C.ECHONL
+ syscall_ICANON = C.ICANON
+ syscall_ISIG = C.ISIG
+ syscall_IEXTEN = C.IEXTEN
+ syscall_CSIZE = C.CSIZE
+ syscall_PARENB = C.PARENB
+ syscall_CS8 = C.CS8
+ syscall_VMIN = C.VMIN
+ syscall_VTIME = C.VTIME
+
+ // on darwin change these to (on *bsd too?):
+ // C.TIOCGETA
+ // C.TIOCSETA
+ syscall_TCGETS = C.TCGETS
+ syscall_TCSETS = C.TCSETS
+)
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_darwin.go b/vendor/github.com/nsf/termbox-go/syscalls_darwin.go
new file mode 100644
index 0000000..25b78f7
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/syscalls_darwin.go
@@ -0,0 +1,41 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs syscalls.go
+
+// +build !amd64
+
+package termbox
+
+type syscall_Termios struct {
+ Iflag uint32
+ Oflag uint32
+ Cflag uint32
+ Lflag uint32
+ Cc [20]uint8
+ Ispeed uint32
+ Ospeed uint32
+}
+
+const (
+ syscall_IGNBRK = 0x1
+ syscall_BRKINT = 0x2
+ syscall_PARMRK = 0x8
+ syscall_ISTRIP = 0x20
+ syscall_INLCR = 0x40
+ syscall_IGNCR = 0x80
+ syscall_ICRNL = 0x100
+ syscall_IXON = 0x200
+ syscall_OPOST = 0x1
+ syscall_ECHO = 0x8
+ syscall_ECHONL = 0x10
+ syscall_ICANON = 0x100
+ syscall_ISIG = 0x80
+ syscall_IEXTEN = 0x400
+ syscall_CSIZE = 0x300
+ syscall_PARENB = 0x1000
+ syscall_CS8 = 0x300
+ syscall_VMIN = 0x10
+ syscall_VTIME = 0x11
+
+ syscall_TCGETS = 0x402c7413
+ syscall_TCSETS = 0x802c7414
+)
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_darwin_amd64.go b/vendor/github.com/nsf/termbox-go/syscalls_darwin_amd64.go
new file mode 100644
index 0000000..11f25be
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/syscalls_darwin_amd64.go
@@ -0,0 +1,40 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs syscalls.go
+
+package termbox
+
+type syscall_Termios struct {
+ Iflag uint64
+ Oflag uint64
+ Cflag uint64
+ Lflag uint64
+ Cc [20]uint8
+ Pad_cgo_0 [4]byte
+ Ispeed uint64
+ Ospeed uint64
+}
+
+const (
+ syscall_IGNBRK = 0x1
+ syscall_BRKINT = 0x2
+ syscall_PARMRK = 0x8
+ syscall_ISTRIP = 0x20
+ syscall_INLCR = 0x40
+ syscall_IGNCR = 0x80
+ syscall_ICRNL = 0x100
+ syscall_IXON = 0x200
+ syscall_OPOST = 0x1
+ syscall_ECHO = 0x8
+ syscall_ECHONL = 0x10
+ syscall_ICANON = 0x100
+ syscall_ISIG = 0x80
+ syscall_IEXTEN = 0x400
+ syscall_CSIZE = 0x300
+ syscall_PARENB = 0x1000
+ syscall_CS8 = 0x300
+ syscall_VMIN = 0x10
+ syscall_VTIME = 0x11
+
+ syscall_TCGETS = 0x40487413
+ syscall_TCSETS = 0x80487414
+)
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_dragonfly.go b/vendor/github.com/nsf/termbox-go/syscalls_dragonfly.go
new file mode 100644
index 0000000..e03624e
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/syscalls_dragonfly.go
@@ -0,0 +1,39 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs syscalls.go
+
+package termbox
+
+type syscall_Termios struct {
+ Iflag uint32
+ Oflag uint32
+ Cflag uint32
+ Lflag uint32
+ Cc [20]uint8
+ Ispeed uint32
+ Ospeed uint32
+}
+
+const (
+ syscall_IGNBRK = 0x1
+ syscall_BRKINT = 0x2
+ syscall_PARMRK = 0x8
+ syscall_ISTRIP = 0x20
+ syscall_INLCR = 0x40
+ syscall_IGNCR = 0x80
+ syscall_ICRNL = 0x100
+ syscall_IXON = 0x200
+ syscall_OPOST = 0x1
+ syscall_ECHO = 0x8
+ syscall_ECHONL = 0x10
+ syscall_ICANON = 0x100
+ syscall_ISIG = 0x80
+ syscall_IEXTEN = 0x400
+ syscall_CSIZE = 0x300
+ syscall_PARENB = 0x1000
+ syscall_CS8 = 0x300
+ syscall_VMIN = 0x10
+ syscall_VTIME = 0x11
+
+ syscall_TCGETS = 0x402c7413
+ syscall_TCSETS = 0x802c7414
+)
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_freebsd.go b/vendor/github.com/nsf/termbox-go/syscalls_freebsd.go
new file mode 100644
index 0000000..e03624e
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/syscalls_freebsd.go
@@ -0,0 +1,39 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs syscalls.go
+
+package termbox
+
+type syscall_Termios struct {
+ Iflag uint32
+ Oflag uint32
+ Cflag uint32
+ Lflag uint32
+ Cc [20]uint8
+ Ispeed uint32
+ Ospeed uint32
+}
+
+const (
+ syscall_IGNBRK = 0x1
+ syscall_BRKINT = 0x2
+ syscall_PARMRK = 0x8
+ syscall_ISTRIP = 0x20
+ syscall_INLCR = 0x40
+ syscall_IGNCR = 0x80
+ syscall_ICRNL = 0x100
+ syscall_IXON = 0x200
+ syscall_OPOST = 0x1
+ syscall_ECHO = 0x8
+ syscall_ECHONL = 0x10
+ syscall_ICANON = 0x100
+ syscall_ISIG = 0x80
+ syscall_IEXTEN = 0x400
+ syscall_CSIZE = 0x300
+ syscall_PARENB = 0x1000
+ syscall_CS8 = 0x300
+ syscall_VMIN = 0x10
+ syscall_VTIME = 0x11
+
+ syscall_TCGETS = 0x402c7413
+ syscall_TCSETS = 0x802c7414
+)
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_linux.go b/vendor/github.com/nsf/termbox-go/syscalls_linux.go
new file mode 100644
index 0000000..b88960d
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/syscalls_linux.go
@@ -0,0 +1,33 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs syscalls.go
+
+package termbox
+
+import "syscall"
+
+type syscall_Termios syscall.Termios
+
+const (
+ syscall_IGNBRK = syscall.IGNBRK
+ syscall_BRKINT = syscall.BRKINT
+ syscall_PARMRK = syscall.PARMRK
+ syscall_ISTRIP = syscall.ISTRIP
+ syscall_INLCR = syscall.INLCR
+ syscall_IGNCR = syscall.IGNCR
+ syscall_ICRNL = syscall.ICRNL
+ syscall_IXON = syscall.IXON
+ syscall_OPOST = syscall.OPOST
+ syscall_ECHO = syscall.ECHO
+ syscall_ECHONL = syscall.ECHONL
+ syscall_ICANON = syscall.ICANON
+ syscall_ISIG = syscall.ISIG
+ syscall_IEXTEN = syscall.IEXTEN
+ syscall_CSIZE = syscall.CSIZE
+ syscall_PARENB = syscall.PARENB
+ syscall_CS8 = syscall.CS8
+ syscall_VMIN = syscall.VMIN
+ syscall_VTIME = syscall.VTIME
+
+ syscall_TCGETS = syscall.TCGETS
+ syscall_TCSETS = syscall.TCSETS
+)
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_netbsd.go b/vendor/github.com/nsf/termbox-go/syscalls_netbsd.go
new file mode 100644
index 0000000..49a3355
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/syscalls_netbsd.go
@@ -0,0 +1,39 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs syscalls.go
+
+package termbox
+
+type syscall_Termios struct {
+ Iflag uint32
+ Oflag uint32
+ Cflag uint32
+ Lflag uint32
+ Cc [20]uint8
+ Ispeed int32
+ Ospeed int32
+}
+
+const (
+ syscall_IGNBRK = 0x1
+ syscall_BRKINT = 0x2
+ syscall_PARMRK = 0x8
+ syscall_ISTRIP = 0x20
+ syscall_INLCR = 0x40
+ syscall_IGNCR = 0x80
+ syscall_ICRNL = 0x100
+ syscall_IXON = 0x200
+ syscall_OPOST = 0x1
+ syscall_ECHO = 0x8
+ syscall_ECHONL = 0x10
+ syscall_ICANON = 0x100
+ syscall_ISIG = 0x80
+ syscall_IEXTEN = 0x400
+ syscall_CSIZE = 0x300
+ syscall_PARENB = 0x1000
+ syscall_CS8 = 0x300
+ syscall_VMIN = 0x10
+ syscall_VTIME = 0x11
+
+ syscall_TCGETS = 0x402c7413
+ syscall_TCSETS = 0x802c7414
+)
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_openbsd.go b/vendor/github.com/nsf/termbox-go/syscalls_openbsd.go
new file mode 100644
index 0000000..49a3355
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/syscalls_openbsd.go
@@ -0,0 +1,39 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs syscalls.go
+
+package termbox
+
+type syscall_Termios struct {
+ Iflag uint32
+ Oflag uint32
+ Cflag uint32
+ Lflag uint32
+ Cc [20]uint8
+ Ispeed int32
+ Ospeed int32
+}
+
+const (
+ syscall_IGNBRK = 0x1
+ syscall_BRKINT = 0x2
+ syscall_PARMRK = 0x8
+ syscall_ISTRIP = 0x20
+ syscall_INLCR = 0x40
+ syscall_IGNCR = 0x80
+ syscall_ICRNL = 0x100
+ syscall_IXON = 0x200
+ syscall_OPOST = 0x1
+ syscall_ECHO = 0x8
+ syscall_ECHONL = 0x10
+ syscall_ICANON = 0x100
+ syscall_ISIG = 0x80
+ syscall_IEXTEN = 0x400
+ syscall_CSIZE = 0x300
+ syscall_PARENB = 0x1000
+ syscall_CS8 = 0x300
+ syscall_VMIN = 0x10
+ syscall_VTIME = 0x11
+
+ syscall_TCGETS = 0x402c7413
+ syscall_TCSETS = 0x802c7414
+)
diff --git a/vendor/github.com/nsf/termbox-go/syscalls_windows.go b/vendor/github.com/nsf/termbox-go/syscalls_windows.go
new file mode 100644
index 0000000..472d002
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/syscalls_windows.go
@@ -0,0 +1,61 @@
+// Created by cgo -godefs - DO NOT EDIT
+// cgo -godefs -- -DUNICODE syscalls.go
+
+package termbox
+
+const (
+ foreground_blue = 0x1
+ foreground_green = 0x2
+ foreground_red = 0x4
+ foreground_intensity = 0x8
+ background_blue = 0x10
+ background_green = 0x20
+ background_red = 0x40
+ background_intensity = 0x80
+ std_input_handle = -0xa
+ std_output_handle = -0xb
+ key_event = 0x1
+ mouse_event = 0x2
+ window_buffer_size_event = 0x4
+ enable_window_input = 0x8
+ enable_mouse_input = 0x10
+ enable_extended_flags = 0x80
+
+ vk_f1 = 0x70
+ vk_f2 = 0x71
+ vk_f3 = 0x72
+ vk_f4 = 0x73
+ vk_f5 = 0x74
+ vk_f6 = 0x75
+ vk_f7 = 0x76
+ vk_f8 = 0x77
+ vk_f9 = 0x78
+ vk_f10 = 0x79
+ vk_f11 = 0x7a
+ vk_f12 = 0x7b
+ vk_insert = 0x2d
+ vk_delete = 0x2e
+ vk_home = 0x24
+ vk_end = 0x23
+ vk_pgup = 0x21
+ vk_pgdn = 0x22
+ vk_arrow_up = 0x26
+ vk_arrow_down = 0x28
+ vk_arrow_left = 0x25
+ vk_arrow_right = 0x27
+ vk_backspace = 0x8
+ vk_tab = 0x9
+ vk_enter = 0xd
+ vk_esc = 0x1b
+ vk_space = 0x20
+
+ left_alt_pressed = 0x2
+ left_ctrl_pressed = 0x8
+ right_alt_pressed = 0x1
+ right_ctrl_pressed = 0x4
+ shift_pressed = 0x10
+
+ generic_read = 0x80000000
+ generic_write = 0x40000000
+ console_textmode_buffer = 0x1
+)
diff --git a/vendor/github.com/nsf/termbox-go/termbox.go b/vendor/github.com/nsf/termbox-go/termbox.go
new file mode 100644
index 0000000..fbe4c3d
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/termbox.go
@@ -0,0 +1,529 @@
+// +build !windows
+
+package termbox
+
+import "unicode/utf8"
+import "bytes"
+import "syscall"
+import "unsafe"
+import "strings"
+import "strconv"
+import "os"
+import "io"
+
+// private API
+
+const (
+ t_enter_ca = iota
+ t_exit_ca
+ t_show_cursor
+ t_hide_cursor
+ t_clear_screen
+ t_sgr0
+ t_underline
+ t_bold
+ t_blink
+ t_reverse
+ t_enter_keypad
+ t_exit_keypad
+ t_enter_mouse
+ t_exit_mouse
+ t_max_funcs
+)
+
+const (
+ coord_invalid = -2
+ attr_invalid = Attribute(0xFFFF)
+)
+
+type input_event struct {
+ data []byte
+ err error
+}
+
+type extract_event_res int
+
+const (
+ event_not_extracted extract_event_res = iota
+ event_extracted
+ esc_wait
+)
+
+var (
+ // term specific sequences
+ keys []string
+ funcs []string
+
+ // termbox inner state
+ orig_tios syscall_Termios
+ back_buffer cellbuf
+ front_buffer cellbuf
+ termw int
+ termh int
+ input_mode = InputEsc
+ output_mode = OutputNormal
+ out *os.File
+ in int
+ lastfg = attr_invalid
+ lastbg = attr_invalid
+ lastx = coord_invalid
+ lasty = coord_invalid
+ cursor_x = cursor_hidden
+ cursor_y = cursor_hidden
+ foreground = ColorDefault
+ background = ColorDefault
+ inbuf = make([]byte, 0, 64)
+ outbuf bytes.Buffer
+ sigwinch = make(chan os.Signal, 1)
+ sigio = make(chan os.Signal, 1)
+ quit = make(chan int)
+ input_comm = make(chan input_event)
+ interrupt_comm = make(chan struct{})
+ intbuf = make([]byte, 0, 16)
+
+ // grayscale indexes
+ grayscale = []Attribute{
+ 0, 17, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244,
+ 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 232,
+ }
+)
+
+func write_cursor(x, y int) {
+ outbuf.WriteString("\033[")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(y+1), 10))
+ outbuf.WriteString(";")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(x+1), 10))
+ outbuf.WriteString("H")
+}
+
+func write_sgr_fg(a Attribute) {
+ switch output_mode {
+ case Output256, Output216, OutputGrayscale:
+ outbuf.WriteString("\033[38;5;")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
+ outbuf.WriteString("m")
+ default:
+ outbuf.WriteString("\033[3")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
+ outbuf.WriteString("m")
+ }
+}
+
+func write_sgr_bg(a Attribute) {
+ switch output_mode {
+ case Output256, Output216, OutputGrayscale:
+ outbuf.WriteString("\033[48;5;")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
+ outbuf.WriteString("m")
+ default:
+ outbuf.WriteString("\033[4")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
+ outbuf.WriteString("m")
+ }
+}
+
+func write_sgr(fg, bg Attribute) {
+ switch output_mode {
+ case Output256, Output216, OutputGrayscale:
+ outbuf.WriteString("\033[38;5;")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10))
+ outbuf.WriteString("m")
+ outbuf.WriteString("\033[48;5;")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10))
+ outbuf.WriteString("m")
+ default:
+ outbuf.WriteString("\033[3")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10))
+ outbuf.WriteString(";4")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10))
+ outbuf.WriteString("m")
+ }
+}
+
+type winsize struct {
+ rows uint16
+ cols uint16
+ xpixels uint16
+ ypixels uint16
+}
+
+func get_term_size(fd uintptr) (int, int) {
+ var sz winsize
+ _, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
+ fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
+ return int(sz.cols), int(sz.rows)
+}
+
+func send_attr(fg, bg Attribute) {
+ if fg == lastfg && bg == lastbg {
+ return
+ }
+
+ outbuf.WriteString(funcs[t_sgr0])
+
+ var fgcol, bgcol Attribute
+
+ switch output_mode {
+ case Output256:
+ fgcol = fg & 0x1FF
+ bgcol = bg & 0x1FF
+ case Output216:
+ fgcol = fg & 0xFF
+ bgcol = bg & 0xFF
+ if fgcol > 216 {
+ fgcol = ColorDefault
+ }
+ if bgcol > 216 {
+ bgcol = ColorDefault
+ }
+ if fgcol != ColorDefault {
+ fgcol += 0x10
+ }
+ if bgcol != ColorDefault {
+ bgcol += 0x10
+ }
+ case OutputGrayscale:
+ fgcol = fg & 0x1F
+ bgcol = bg & 0x1F
+ if fgcol > 26 {
+ fgcol = ColorDefault
+ }
+ if bgcol > 26 {
+ bgcol = ColorDefault
+ }
+ if fgcol != ColorDefault {
+ fgcol = grayscale[fgcol]
+ }
+ if bgcol != ColorDefault {
+ bgcol = grayscale[bgcol]
+ }
+ default:
+ fgcol = fg & 0x0F
+ bgcol = bg & 0x0F
+ }
+
+ if fgcol != ColorDefault {
+ if bgcol != ColorDefault {
+ write_sgr(fgcol, bgcol)
+ } else {
+ write_sgr_fg(fgcol)
+ }
+ } else if bgcol != ColorDefault {
+ write_sgr_bg(bgcol)
+ }
+
+ if fg&AttrBold != 0 {
+ outbuf.WriteString(funcs[t_bold])
+ }
+ if bg&AttrBold != 0 {
+ outbuf.WriteString(funcs[t_blink])
+ }
+ if fg&AttrUnderline != 0 {
+ outbuf.WriteString(funcs[t_underline])
+ }
+ if fg&AttrReverse|bg&AttrReverse != 0 {
+ outbuf.WriteString(funcs[t_reverse])
+ }
+
+ lastfg, lastbg = fg, bg
+}
+
+func send_char(x, y int, ch rune) {
+ var buf [8]byte
+ n := utf8.EncodeRune(buf[:], ch)
+ if x-1 != lastx || y != lasty {
+ write_cursor(x, y)
+ }
+ lastx, lasty = x, y
+ outbuf.Write(buf[:n])
+}
+
+func flush() error {
+ _, err := io.Copy(out, &outbuf)
+ outbuf.Reset()
+ return err
+}
+
+func send_clear() error {
+ send_attr(foreground, background)
+ outbuf.WriteString(funcs[t_clear_screen])
+ if !is_cursor_hidden(cursor_x, cursor_y) {
+ write_cursor(cursor_x, cursor_y)
+ }
+
+ // we need to invalidate cursor position too and these two vars are
+ // used only for simple cursor positioning optimization, cursor
+ // actually may be in the correct place, but we simply discard
+ // optimization once and it gives us simple solution for the case when
+ // cursor moved
+ lastx = coord_invalid
+ lasty = coord_invalid
+
+ return flush()
+}
+
+func update_size_maybe() error {
+ w, h := get_term_size(out.Fd())
+ if w != termw || h != termh {
+ termw, termh = w, h
+ back_buffer.resize(termw, termh)
+ front_buffer.resize(termw, termh)
+ front_buffer.clear()
+ return send_clear()
+ }
+ return nil
+}
+
+func tcsetattr(fd uintptr, termios *syscall_Termios) error {
+ r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
+ fd, uintptr(syscall_TCSETS), uintptr(unsafe.Pointer(termios)))
+ if r != 0 {
+ return os.NewSyscallError("SYS_IOCTL", e)
+ }
+ return nil
+}
+
+func tcgetattr(fd uintptr, termios *syscall_Termios) error {
+ r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
+ fd, uintptr(syscall_TCGETS), uintptr(unsafe.Pointer(termios)))
+ if r != 0 {
+ return os.NewSyscallError("SYS_IOCTL", e)
+ }
+ return nil
+}
+
+func parse_mouse_event(event *Event, buf string) (int, bool) {
+ if strings.HasPrefix(buf, "\033[M") && len(buf) >= 6 {
+ // X10 mouse encoding, the simplest one
+ // \033 [ M Cb Cx Cy
+ b := buf[3] - 32
+ switch b & 3 {
+ case 0:
+ if b&64 != 0 {
+ event.Key = MouseWheelUp
+ } else {
+ event.Key = MouseLeft
+ }
+ case 1:
+ if b&64 != 0 {
+ event.Key = MouseWheelDown
+ } else {
+ event.Key = MouseMiddle
+ }
+ case 2:
+ event.Key = MouseRight
+ case 3:
+ event.Key = MouseRelease
+ default:
+ return 6, false
+ }
+ event.Type = EventMouse // KeyEvent by default
+ if b&32 != 0 {
+ event.Mod |= ModMotion
+ }
+
+ // the coord is 1,1 for upper left
+ event.MouseX = int(buf[4]) - 1 - 32
+ event.MouseY = int(buf[5]) - 1 - 32
+ return 6, true
+ } else if strings.HasPrefix(buf, "\033[<") || strings.HasPrefix(buf, "\033[") {
+ // xterm 1006 extended mode or urxvt 1015 extended mode
+ // xterm: \033 [ < Cb ; Cx ; Cy (M or m)
+ // urxvt: \033 [ Cb ; Cx ; Cy M
+
+ // find the first M or m, that's where we stop
+ mi := strings.IndexAny(buf, "Mm")
+ if mi == -1 {
+ return 0, false
+ }
+
+ // whether it's a capital M or not
+ isM := buf[mi] == 'M'
+
+ // whether it's urxvt or not
+ isU := false
+
+ // buf[2] is safe here, because having M or m found means we have at
+ // least 3 bytes in a string
+ if buf[2] == '<' {
+ buf = buf[3:mi]
+ } else {
+ isU = true
+ buf = buf[2:mi]
+ }
+
+ s1 := strings.Index(buf, ";")
+ s2 := strings.LastIndex(buf, ";")
+ // not found or only one ';'
+ if s1 == -1 || s2 == -1 || s1 == s2 {
+ return 0, false
+ }
+
+ n1, err := strconv.ParseInt(buf[0:s1], 10, 64)
+ if err != nil {
+ return 0, false
+ }
+ n2, err := strconv.ParseInt(buf[s1+1:s2], 10, 64)
+ if err != nil {
+ return 0, false
+ }
+ n3, err := strconv.ParseInt(buf[s2+1:], 10, 64)
+ if err != nil {
+ return 0, false
+ }
+
+ // on urxvt, first number is encoded exactly as in X10, but we need to
+ // make it zero-based, on xterm it is zero-based already
+ if isU {
+ n1 -= 32
+ }
+ switch n1 & 3 {
+ case 0:
+ if n1&64 != 0 {
+ event.Key = MouseWheelUp
+ } else {
+ event.Key = MouseLeft
+ }
+ case 1:
+ if n1&64 != 0 {
+ event.Key = MouseWheelDown
+ } else {
+ event.Key = MouseMiddle
+ }
+ case 2:
+ event.Key = MouseRight
+ case 3:
+ event.Key = MouseRelease
+ default:
+ return mi + 1, false
+ }
+ if !isM {
+ // on xterm mouse release is signaled by lowercase m
+ event.Key = MouseRelease
+ }
+
+ event.Type = EventMouse // KeyEvent by default
+ if n1&32 != 0 {
+ event.Mod |= ModMotion
+ }
+
+ event.MouseX = int(n2) - 1
+ event.MouseY = int(n3) - 1
+ return mi + 1, true
+ }
+
+ return 0, false
+}
+
+func parse_escape_sequence(event *Event, buf []byte) (int, bool) {
+ bufstr := string(buf)
+ for i, key := range keys {
+ if strings.HasPrefix(bufstr, key) {
+ event.Ch = 0
+ event.Key = Key(0xFFFF - i)
+ return len(key), true
+ }
+ }
+
+ // if none of the keys match, let's try mouse sequences
+ return parse_mouse_event(event, bufstr)
+}
+
+func extract_raw_event(data []byte, event *Event) bool {
+ if len(inbuf) == 0 {
+ return false
+ }
+
+ n := len(data)
+ if n == 0 {
+ return false
+ }
+
+ n = copy(data, inbuf)
+ copy(inbuf, inbuf[n:])
+ inbuf = inbuf[:len(inbuf)-n]
+
+ event.N = n
+ event.Type = EventRaw
+ return true
+}
+
+func extract_event(inbuf []byte, event *Event, allow_esc_wait bool) extract_event_res {
+ if len(inbuf) == 0 {
+ event.N = 0
+ return event_not_extracted
+ }
+
+ if inbuf[0] == '\033' {
+ // possible escape sequence
+ if n, ok := parse_escape_sequence(event, inbuf); n != 0 {
+ event.N = n
+ if ok {
+ return event_extracted
+ } else {
+ return event_not_extracted
+ }
+ }
+
+ // possible partially read escape sequence; trigger a wait if appropriate
+ if enable_wait_for_escape_sequence() && allow_esc_wait {
+ event.N = 0
+ return esc_wait
+ }
+
+ // it's not escape sequence, then it's Alt or Esc, check input_mode
+ switch {
+ case input_mode&InputEsc != 0:
+ // if we're in escape mode, fill Esc event, pop buffer, return success
+ event.Ch = 0
+ event.Key = KeyEsc
+ event.Mod = 0
+ event.N = 1
+ return event_extracted
+ case input_mode&InputAlt != 0:
+ // if we're in alt mode, set Alt modifier to event and redo parsing
+ event.Mod = ModAlt
+ status := extract_event(inbuf[1:], event, false)
+ if status == event_extracted {
+ event.N++
+ } else {
+ event.N = 0
+ }
+ return status
+ default:
+ panic("unreachable")
+ }
+ }
+
+ // if we're here, this is not an escape sequence and not an alt sequence
+ // so, it's a FUNCTIONAL KEY or a UNICODE character
+
+ // first of all check if it's a functional key
+ if Key(inbuf[0]) <= KeySpace || Key(inbuf[0]) == KeyBackspace2 {
+ // fill event, pop buffer, return success
+ event.Ch = 0
+ event.Key = Key(inbuf[0])
+ event.N = 1
+ return event_extracted
+ }
+
+ // the only possible option is utf8 rune
+ if r, n := utf8.DecodeRune(inbuf); r != utf8.RuneError {
+ event.Ch = r
+ event.Key = 0
+ event.N = n
+ return event_extracted
+ }
+
+ return event_not_extracted
+}
+
+func fcntl(fd int, cmd int, arg int) (val int, err error) {
+ r, _, e := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(cmd),
+ uintptr(arg))
+ val = int(r)
+ if e != 0 {
+ err = e
+ }
+ return
+}
diff --git a/vendor/github.com/nsf/termbox-go/termbox_common.go b/vendor/github.com/nsf/termbox-go/termbox_common.go
new file mode 100644
index 0000000..c3355cc
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/termbox_common.go
@@ -0,0 +1,59 @@
+package termbox
+
+// private API, common OS agnostic part
+
+type cellbuf struct {
+ width int
+ height int
+ cells []Cell
+}
+
+func (this *cellbuf) init(width, height int) {
+ this.width = width
+ this.height = height
+ this.cells = make([]Cell, width*height)
+}
+
+func (this *cellbuf) resize(width, height int) {
+ if this.width == width && this.height == height {
+ return
+ }
+
+ oldw := this.width
+ oldh := this.height
+ oldcells := this.cells
+
+ this.init(width, height)
+ this.clear()
+
+ minw, minh := oldw, oldh
+
+ if width < minw {
+ minw = width
+ }
+ if height < minh {
+ minh = height
+ }
+
+ for i := 0; i < minh; i++ {
+ srco, dsto := i*oldw, i*width
+ src := oldcells[srco : srco+minw]
+ dst := this.cells[dsto : dsto+minw]
+ copy(dst, src)
+ }
+}
+
+func (this *cellbuf) clear() {
+ for i := range this.cells {
+ c := &this.cells[i]
+ c.Ch = ' '
+ c.Fg = foreground
+ c.Bg = background
+ }
+}
+
+const cursor_hidden = -1
+
+func is_cursor_hidden(x, y int) bool {
+ return x == cursor_hidden || y == cursor_hidden
+}
diff --git a/vendor/github.com/nsf/termbox-go/termbox_windows.go b/vendor/github.com/nsf/termbox-go/termbox_windows.go
new file mode 100644
index 0000000..7752a17
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/termbox_windows.go
@@ -0,0 +1,915 @@
+package termbox
+
+import "math"
+import "syscall"
+import "unsafe"
+import "unicode/utf16"
+import "github.com/mattn/go-runewidth"
+
+type (
+ wchar uint16
+ short int16
+ dword uint32
+ word uint16
+ char_info struct {
+ char wchar
+ attr word
+ }
+ coord struct {
+ x short
+ y short
+ }
+ small_rect struct {
+ left short
+ top short
+ right short
+ bottom short
+ }
+ console_screen_buffer_info struct {
+ size coord
+ cursor_position coord
+ attributes word
+ window small_rect
+ maximum_window_size coord
+ }
+ console_cursor_info struct {
+ size dword
+ visible int32
+ }
+ input_record struct {
+ event_type word
+ _ [2]byte
+ event [16]byte
+ }
+ key_event_record struct {
+ key_down int32
+ repeat_count word
+ virtual_key_code word
+ virtual_scan_code word
+ unicode_char wchar
+ control_key_state dword
+ }
+ window_buffer_size_record struct {
+ size coord
+ }
+ mouse_event_record struct {
+ mouse_pos coord
+ button_state dword
+ control_key_state dword
+ event_flags dword
+ }
+ console_font_info struct {
+ font uint32
+ font_size coord
+ }
+)
+
+const (
+ mouse_lmb = 0x1
+ mouse_rmb = 0x2
+ mouse_mmb = 0x4 | 0x8 | 0x10
+ SM_CXMIN = 28
+ SM_CYMIN = 29
+)
+
+func (this coord) uintptr() uintptr {
+ return uintptr(*(*int32)(unsafe.Pointer(&this)))
+}
+
+var kernel32 = syscall.NewLazyDLL("kernel32.dll")
+var moduser32 = syscall.NewLazyDLL("user32.dll")
+var is_cjk = runewidth.IsEastAsian()
+
+var (
+ proc_set_console_active_screen_buffer = kernel32.NewProc("SetConsoleActiveScreenBuffer")
+ proc_set_console_screen_buffer_size = kernel32.NewProc("SetConsoleScreenBufferSize")
+ proc_create_console_screen_buffer = kernel32.NewProc("CreateConsoleScreenBuffer")
+ proc_get_console_screen_buffer_info = kernel32.NewProc("GetConsoleScreenBufferInfo")
+ proc_write_console_output = kernel32.NewProc("WriteConsoleOutputW")
+ proc_write_console_output_character = kernel32.NewProc("WriteConsoleOutputCharacterW")
+ proc_write_console_output_attribute = kernel32.NewProc("WriteConsoleOutputAttribute")
+ proc_set_console_cursor_info = kernel32.NewProc("SetConsoleCursorInfo")
+ proc_set_console_cursor_position = kernel32.NewProc("SetConsoleCursorPosition")
+ proc_get_console_cursor_info = kernel32.NewProc("GetConsoleCursorInfo")
+ proc_read_console_input = kernel32.NewProc("ReadConsoleInputW")
+ proc_get_console_mode = kernel32.NewProc("GetConsoleMode")
+ proc_set_console_mode = kernel32.NewProc("SetConsoleMode")
+ proc_fill_console_output_character = kernel32.NewProc("FillConsoleOutputCharacterW")
+ proc_fill_console_output_attribute = kernel32.NewProc("FillConsoleOutputAttribute")
+ proc_create_event = kernel32.NewProc("CreateEventW")
+ proc_wait_for_multiple_objects = kernel32.NewProc("WaitForMultipleObjects")
+ proc_set_event = kernel32.NewProc("SetEvent")
+ proc_get_current_console_font = kernel32.NewProc("GetCurrentConsoleFont")
+ get_system_metrics = moduser32.NewProc("GetSystemMetrics")
+)
+
+func set_console_active_screen_buffer(h syscall.Handle) (err error) {
+ r0, _, e1 := syscall.Syscall(proc_set_console_active_screen_buffer.Addr(),
+ 1, uintptr(h), 0, 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func set_console_screen_buffer_size(h syscall.Handle, size coord) (err error) {
+ r0, _, e1 := syscall.Syscall(proc_set_console_screen_buffer_size.Addr(),
+ 2, uintptr(h), size.uintptr(), 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func create_console_screen_buffer() (h syscall.Handle, err error) {
+ r0, _, e1 := syscall.Syscall6(proc_create_console_screen_buffer.Addr(),
+ 5, uintptr(generic_read|generic_write), 0, 0, console_textmode_buffer, 0, 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return syscall.Handle(r0), err
+}
+
+func get_console_screen_buffer_info(h syscall.Handle, info *console_screen_buffer_info) (err error) {
+ r0, _, e1 := syscall.Syscall(proc_get_console_screen_buffer_info.Addr(),
+ 2, uintptr(h), uintptr(unsafe.Pointer(info)), 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func write_console_output(h syscall.Handle, chars []char_info, dst small_rect) (err error) {
+ tmp_coord = coord{dst.right - dst.left + 1, dst.bottom - dst.top + 1}
+ tmp_rect = dst
+ r0, _, e1 := syscall.Syscall6(proc_write_console_output.Addr(),
+ 5, uintptr(h), uintptr(unsafe.Pointer(&chars[0])), tmp_coord.uintptr(),
+ tmp_coord0.uintptr(), uintptr(unsafe.Pointer(&tmp_rect)), 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func write_console_output_character(h syscall.Handle, chars []wchar, pos coord) (err error) {
+ r0, _, e1 := syscall.Syscall6(proc_write_console_output_character.Addr(),
+ 5, uintptr(h), uintptr(unsafe.Pointer(&chars[0])), uintptr(len(chars)),
+ pos.uintptr(), uintptr(unsafe.Pointer(&tmp_arg)), 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func write_console_output_attribute(h syscall.Handle, attrs []word, pos coord) (err error) {
+ r0, _, e1 := syscall.Syscall6(proc_write_console_output_attribute.Addr(),
+ 5, uintptr(h), uintptr(unsafe.Pointer(&attrs[0])), uintptr(len(attrs)),
+ pos.uintptr(), uintptr(unsafe.Pointer(&tmp_arg)), 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func set_console_cursor_info(h syscall.Handle, info *console_cursor_info) (err error) {
+ r0, _, e1 := syscall.Syscall(proc_set_console_cursor_info.Addr(),
+ 2, uintptr(h), uintptr(unsafe.Pointer(info)), 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func get_console_cursor_info(h syscall.Handle, info *console_cursor_info) (err error) {
+ r0, _, e1 := syscall.Syscall(proc_get_console_cursor_info.Addr(),
+ 2, uintptr(h), uintptr(unsafe.Pointer(info)), 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func set_console_cursor_position(h syscall.Handle, pos coord) (err error) {
+ r0, _, e1 := syscall.Syscall(proc_set_console_cursor_position.Addr(),
+ 2, uintptr(h), pos.uintptr(), 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func read_console_input(h syscall.Handle, record *input_record) (err error) {
+ r0, _, e1 := syscall.Syscall6(proc_read_console_input.Addr(),
+ 4, uintptr(h), uintptr(unsafe.Pointer(record)), 1, uintptr(unsafe.Pointer(&tmp_arg)), 0, 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func get_console_mode(h syscall.Handle, mode *dword) (err error) {
+ r0, _, e1 := syscall.Syscall(proc_get_console_mode.Addr(),
+ 2, uintptr(h), uintptr(unsafe.Pointer(mode)), 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func set_console_mode(h syscall.Handle, mode dword) (err error) {
+ r0, _, e1 := syscall.Syscall(proc_set_console_mode.Addr(),
+ 2, uintptr(h), uintptr(mode), 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func fill_console_output_character(h syscall.Handle, char wchar, n int) (err error) {
+ r0, _, e1 := syscall.Syscall6(proc_fill_console_output_character.Addr(),
+ 5, uintptr(h), uintptr(char), uintptr(n), tmp_coord.uintptr(),
+ uintptr(unsafe.Pointer(&tmp_arg)), 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func fill_console_output_attribute(h syscall.Handle, attr word, n int) (err error) {
+ r0, _, e1 := syscall.Syscall6(proc_fill_console_output_attribute.Addr(),
+ 5, uintptr(h), uintptr(attr), uintptr(n), tmp_coord.uintptr(),
+ uintptr(unsafe.Pointer(&tmp_arg)), 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func create_event() (out syscall.Handle, err error) {
+ r0, _, e1 := syscall.Syscall6(proc_create_event.Addr(),
+ 4, 0, 0, 0, 0, 0, 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return syscall.Handle(r0), err
+}
+
+func wait_for_multiple_objects(objects []syscall.Handle) (err error) {
+ r0, _, e1 := syscall.Syscall6(proc_wait_for_multiple_objects.Addr(),
+ 4, uintptr(len(objects)), uintptr(unsafe.Pointer(&objects[0])),
+ 0, 0xFFFFFFFF, 0, 0)
+ if uint32(r0) == 0xFFFFFFFF {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func set_event(ev syscall.Handle) (err error) {
+ r0, _, e1 := syscall.Syscall(proc_set_event.Addr(),
+ 1, uintptr(ev), 0, 0)
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func get_current_console_font(h syscall.Handle, info *console_font_info) (err error) {
+ r0, _, e1 := syscall.Syscall(proc_get_current_console_font.Addr(),
+ 3, uintptr(h), 0, uintptr(unsafe.Pointer(info)))
+ if int(r0) == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+type diff_msg struct {
+ pos short
+ lines short
+ chars []char_info
+}
+
+type input_event struct {
+ event Event
+ err error
+}
+
+var (
+ orig_cursor_info console_cursor_info
+ orig_size coord
+ orig_mode dword
+ orig_screen syscall.Handle
+ back_buffer cellbuf
+ front_buffer cellbuf
+ term_size coord
+ input_mode = InputEsc
+ cursor_x = cursor_hidden
+ cursor_y = cursor_hidden
+ foreground = ColorDefault
+ background = ColorDefault
+ in syscall.Handle
+ out syscall.Handle
+ interrupt syscall.Handle
+ charbuf []char_info
+ diffbuf []diff_msg
+ beg_x = -1
+ beg_y = -1
+ beg_i = -1
+ input_comm = make(chan Event)
+ interrupt_comm = make(chan struct{})
+ cancel_comm = make(chan bool, 1)
+ cancel_done_comm = make(chan bool)
+ alt_mode_esc = false
+
+ // these ones just to prevent heap allocs at all costs
+ tmp_info console_screen_buffer_info
+ tmp_arg dword
+ tmp_coord0 = coord{0, 0}
+ tmp_coord = coord{0, 0}
+ tmp_rect = small_rect{0, 0, 0, 0}
+ tmp_finfo console_font_info
+)
+
+func get_cursor_position(out syscall.Handle) coord {
+ err := get_console_screen_buffer_info(out, &tmp_info)
+ if err != nil {
+ panic(err)
+ }
+ return tmp_info.cursor_position
+}
+
+func get_term_size(out syscall.Handle) coord {
+ err := get_console_screen_buffer_info(out, &tmp_info)
+ if err != nil {
+ panic(err)
+ }
+ return tmp_info.size
+}
+
+func get_win_min_size(out syscall.Handle) coord {
+ x, _, err := get_system_metrics.Call(SM_CXMIN)
+ y, _, err := get_system_metrics.Call(SM_CYMIN)
+
+ if x == 0 || y == 0 {
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ err1 := get_current_console_font(out, &tmp_finfo)
+ if err1 != nil {
+ panic(err1)
+ }
+
+ return coord{
+ x: short(math.Ceil(float64(x) / float64(tmp_finfo.font_size.x))),
+ y: short(math.Ceil(float64(y) / float64(tmp_finfo.font_size.y))),
+ }
+}
+
+func get_win_size(out syscall.Handle) coord {
+ err := get_console_screen_buffer_info(out, &tmp_info)
+ if err != nil {
+ panic(err)
+ }
+
+ min_size := get_win_min_size(out)
+
+ size := coord{
+ x: tmp_info.window.right - tmp_info.window.left + 1,
+ y: tmp_info.window.bottom - tmp_info.window.top + 1,
+ }
+
+ if size.x < min_size.x {
+ size.x = min_size.x
+ }
+
+ if size.y < min_size.y {
+ size.y = min_size.y
+ }
+
+ return size
+}
+
+func update_size_maybe() {
+ size := get_win_size(out)
+ if size.x != term_size.x || size.y != term_size.y {
+ set_console_screen_buffer_size(out, size)
+ term_size = size
+ back_buffer.resize(int(size.x), int(size.y))
+ front_buffer.resize(int(size.x), int(size.y))
+ front_buffer.clear()
+ clear()
+
+ area := int(size.x) * int(size.y)
+ if cap(charbuf) < area {
+ charbuf = make([]char_info, 0, area)
+ }
+ }
+}
+
+var color_table_bg = []word{
+ 0, // default (black)
+ 0, // black
+ background_red,
+ background_green,
+ background_red | background_green, // yellow
+ background_blue,
+ background_red | background_blue, // magenta
+ background_green | background_blue, // cyan
+ background_red | background_blue | background_green, // white
+}
+
+var color_table_fg = []word{
+ foreground_red | foreground_blue | foreground_green, // default (white)
+ 0,
+ foreground_red,
+ foreground_green,
+ foreground_red | foreground_green, // yellow
+ foreground_blue,
+ foreground_red | foreground_blue, // magenta
+ foreground_green | foreground_blue, // cyan
+ foreground_red | foreground_blue | foreground_green, // white
+}
+
+const (
+ replacement_char = '\uFFFD'
+ max_rune = '\U0010FFFF'
+ surr1 = 0xd800
+ surr2 = 0xdc00
+ surr3 = 0xe000
+ surr_self = 0x10000
+)
+
+func append_diff_line(y int) int {
+ n := 0
+ for x := 0; x < front_buffer.width; {
+ cell_offset := y*front_buffer.width + x
+ back := &back_buffer.cells[cell_offset]
+ front := &front_buffer.cells[cell_offset]
+ attr, char := cell_to_char_info(*back)
+ charbuf = append(charbuf, char_info{attr: attr, char: char[0]})
+ *front = *back
+ n++
+ w := runewidth.RuneWidth(back.Ch)
+ if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(back.Ch) {
+ w = 1
+ }
+ x += w
+ // If not CJK, fill trailing space with whitespace
+ if !is_cjk && w == 2 {
+ charbuf = append(charbuf, char_info{attr: attr, char: ' '})
+ }
+ }
+ return n
+}
+
+// compares 'back_buffer' with 'front_buffer' and prepares all changes in the form of
+// 'diff_msg's in the 'diff_buf'
+func prepare_diff_messages() {
+ // clear buffers
+ diffbuf = diffbuf[:0]
+ charbuf = charbuf[:0]
+
+ var diff diff_msg
+ gbeg := 0
+ for y := 0; y < front_buffer.height; y++ {
+ same := true
+ line_offset := y * front_buffer.width
+ for x := 0; x < front_buffer.width; x++ {
+ cell_offset := line_offset + x
+ back := &back_buffer.cells[cell_offset]
+ front := &front_buffer.cells[cell_offset]
+ if *back != *front {
+ same = false
+ break
+ }
+ }
+ if same && diff.lines > 0 {
+ diffbuf = append(diffbuf, diff)
+ diff = diff_msg{}
+ }
+ if !same {
+ beg := len(charbuf)
+ end := beg + append_diff_line(y)
+ if diff.lines == 0 {
+ diff.pos = short(y)
+ gbeg = beg
+ }
+ diff.lines++
+ diff.chars = charbuf[gbeg:end]
+ }
+ }
+ if diff.lines > 0 {
+ diffbuf = append(diffbuf, diff)
+ diff = diff_msg{}
+ }
+}
+
+func get_ct(table []word, idx int) word {
+ idx = idx & 0x0F
+ if idx >= len(table) {
+ idx = len(table) - 1
+ }
+ return table[idx]
+}
+
+func cell_to_char_info(c Cell) (attr word, wc [2]wchar) {
+ attr = get_ct(color_table_fg, int(c.Fg)) | get_ct(color_table_bg, int(c.Bg))
+ if c.Fg&AttrReverse|c.Bg&AttrReverse != 0 {
+ attr = (attr&0xF0)>>4 | (attr&0x0F)<<4
+ }
+ if c.Fg&AttrBold != 0 {
+ attr |= foreground_intensity
+ }
+ if c.Bg&AttrBold != 0 {
+ attr |= background_intensity
+ }
+
+ r0, r1 := utf16.EncodeRune(c.Ch)
+ if r0 == 0xFFFD {
+ wc[0] = wchar(c.Ch)
+ wc[1] = ' '
+ } else {
+ wc[0] = wchar(r0)
+ wc[1] = wchar(r1)
+ }
+ return
+}
+
+func move_cursor(x, y int) {
+ err := set_console_cursor_position(out, coord{short(x), short(y)})
+ if err != nil {
+ panic(err)
+ }
+}
+
+func show_cursor(visible bool) {
+ var v int32
+ if visible {
+ v = 1
+ }
+
+ var info console_cursor_info
+ info.size = 100
+ info.visible = v
+ err := set_console_cursor_info(out, &info)
+ if err != nil {
+ panic(err)
+ }
+}
+
+func clear() {
+ var err error
+ attr, char := cell_to_char_info(Cell{
+ ' ',
+ foreground,
+ background,
+ })
+
+ area := int(term_size.x) * int(term_size.y)
+ err = fill_console_output_attribute(out, attr, area)
+ if err != nil {
+ panic(err)
+ }
+ err = fill_console_output_character(out, char[0], area)
+ if err != nil {
+ panic(err)
+ }
+ if !is_cursor_hidden(cursor_x, cursor_y) {
+ move_cursor(cursor_x, cursor_y)
+ }
+}
+
+func key_event_record_to_event(r *key_event_record) (Event, bool) {
+ if r.key_down == 0 {
+ return Event{}, false
+ }
+
+ e := Event{Type: EventKey}
+ if input_mode&InputAlt != 0 {
+ if alt_mode_esc {
+ e.Mod = ModAlt
+ alt_mode_esc = false
+ }
+ if r.control_key_state&(left_alt_pressed|right_alt_pressed) != 0 {
+ e.Mod = ModAlt
+ }
+ }
+
+ ctrlpressed := r.control_key_state&(left_ctrl_pressed|right_ctrl_pressed) != 0
+
+ if r.virtual_key_code >= vk_f1 && r.virtual_key_code <= vk_f12 {
+ switch r.virtual_key_code {
+ case vk_f1:
+ e.Key = KeyF1
+ case vk_f2:
+ e.Key = KeyF2
+ case vk_f3:
+ e.Key = KeyF3
+ case vk_f4:
+ e.Key = KeyF4
+ case vk_f5:
+ e.Key = KeyF5
+ case vk_f6:
+ e.Key = KeyF6
+ case vk_f7:
+ e.Key = KeyF7
+ case vk_f8:
+ e.Key = KeyF8
+ case vk_f9:
+ e.Key = KeyF9
+ case vk_f10:
+ e.Key = KeyF10
+ case vk_f11:
+ e.Key = KeyF11
+ case vk_f12:
+ e.Key = KeyF12
+ default:
+ panic("unreachable")
+ }
+
+ return e, true
+ }
+
+ if r.virtual_key_code <= vk_delete {
+ switch r.virtual_key_code {
+ case vk_insert:
+ e.Key = KeyInsert
+ case vk_delete:
+ e.Key = KeyDelete
+ case vk_home:
+ e.Key = KeyHome
+ case vk_end:
+ e.Key = KeyEnd
+ case vk_pgup:
+ e.Key = KeyPgup
+ case vk_pgdn:
+ e.Key = KeyPgdn
+ case vk_arrow_up:
+ e.Key = KeyArrowUp
+ case vk_arrow_down:
+ e.Key = KeyArrowDown
+ case vk_arrow_left:
+ e.Key = KeyArrowLeft
+ case vk_arrow_right:
+ e.Key = KeyArrowRight
+ case vk_backspace:
+ if ctrlpressed {
+ e.Key = KeyBackspace2
+ } else {
+ e.Key = KeyBackspace
+ }
+ case vk_tab:
+ e.Key = KeyTab
+ case vk_enter:
+ e.Key = KeyEnter
+ case vk_esc:
+ switch {
+ case input_mode&InputEsc != 0:
+ e.Key = KeyEsc
+ case input_mode&InputAlt != 0:
+ alt_mode_esc = true
+ return Event{}, false
+ }
+ case vk_space:
+ if ctrlpressed {
+ // manual return here, because KeyCtrlSpace is zero
+ e.Key = KeyCtrlSpace
+ return e, true
+ } else {
+ e.Key = KeySpace
+ }
+ }
+
+ if e.Key != 0 {
+ return e, true
+ }
+ }
+
+ if ctrlpressed {
+ if Key(r.unicode_char) >= KeyCtrlA && Key(r.unicode_char) <= KeyCtrlRsqBracket {
+ e.Key = Key(r.unicode_char)
+ if input_mode&InputAlt != 0 && e.Key == KeyEsc {
+ alt_mode_esc = true
+ return Event{}, false
+ }
+ return e, true
+ }
+ switch r.virtual_key_code {
+ case 192, 50:
+ // manual return here, because KeyCtrl2 is zero
+ e.Key = KeyCtrl2
+ return e, true
+ case 51:
+ if input_mode&InputAlt != 0 {
+ alt_mode_esc = true
+ return Event{}, false
+ }
+ e.Key = KeyCtrl3
+ case 52:
+ e.Key = KeyCtrl4
+ case 53:
+ e.Key = KeyCtrl5
+ case 54:
+ e.Key = KeyCtrl6
+ case 189, 191, 55:
+ e.Key = KeyCtrl7
+ case 8, 56:
+ e.Key = KeyCtrl8
+ }
+
+ if e.Key != 0 {
+ return e, true
+ }
+ }
+
+ if r.unicode_char != 0 {
+ e.Ch = rune(r.unicode_char)
+ return e, true
+ }
+
+ return Event{}, false
+}
+
+func input_event_producer() {
+ var r input_record
+ var err error
+ var last_button Key
+ var last_button_pressed Key
+ var last_state = dword(0)
+ var last_x, last_y = -1, -1
+ handles := []syscall.Handle{in, interrupt}
+ for {
+ err = wait_for_multiple_objects(handles)
+ if err != nil {
+ input_comm <- Event{Type: EventError, Err: err}
+ }
+
+ select {
+ case <-cancel_comm:
+ cancel_done_comm <- true
+ return
+ default:
+ }
+
+ err = read_console_input(in, &r)
+ if err != nil {
+ input_comm <- Event{Type: EventError, Err: err}
+ }
+
+ switch r.event_type {
+ case key_event:
+ kr := (*key_event_record)(unsafe.Pointer(&r.event))
+ ev, ok := key_event_record_to_event(kr)
+ if ok {
+ for i := 0; i < int(kr.repeat_count); i++ {
+ input_comm <- ev
+ }
+ }
+ case window_buffer_size_event:
+ sr := *(*window_buffer_size_record)(unsafe.Pointer(&r.event))
+ input_comm <- Event{
+ Type: EventResize,
+ Width: int(sr.size.x),
+ Height: int(sr.size.y),
+ }
+ case mouse_event:
+ mr := *(*mouse_event_record)(unsafe.Pointer(&r.event))
+ ev := Event{Type: EventMouse}
+ switch mr.event_flags {
+ case 0, 2:
+ // single or double click
+ cur_state := mr.button_state
+ switch {
+ case last_state&mouse_lmb == 0 && cur_state&mouse_lmb != 0:
+ last_button = MouseLeft
+ last_button_pressed = last_button
+ case last_state&mouse_rmb == 0 && cur_state&mouse_rmb != 0:
+ last_button = MouseRight
+ last_button_pressed = last_button
+ case last_state&mouse_mmb == 0 && cur_state&mouse_mmb != 0:
+ last_button = MouseMiddle
+ last_button_pressed = last_button
+ case last_state&mouse_lmb != 0 && cur_state&mouse_lmb == 0:
+ last_button = MouseRelease
+ case last_state&mouse_rmb != 0 && cur_state&mouse_rmb == 0:
+ last_button = MouseRelease
+ case last_state&mouse_mmb != 0 && cur_state&mouse_mmb == 0:
+ last_button = MouseRelease
+ default:
+ last_state = cur_state
+ continue
+ }
+ last_state = cur_state
+ ev.Key = last_button
+ last_x, last_y = int(mr.mouse_pos.x), int(mr.mouse_pos.y)
+ ev.MouseX = last_x
+ ev.MouseY = last_y
+ case 1:
+ // mouse motion
+ x, y := int(mr.mouse_pos.x), int(mr.mouse_pos.y)
+ if last_state != 0 && (last_x != x || last_y != y) {
+ ev.Key = last_button_pressed
+ ev.Mod = ModMotion
+ ev.MouseX = x
+ ev.MouseY = y
+ last_x, last_y = x, y
+ } else {
+ ev.Type = EventNone
+ }
+ case 4:
+ // mouse wheel
+ n := int16(mr.button_state >> 16)
+ if n > 0 {
+ ev.Key = MouseWheelUp
+ } else {
+ ev.Key = MouseWheelDown
+ }
+ last_x, last_y = int(mr.mouse_pos.x), int(mr.mouse_pos.y)
+ ev.MouseX = last_x
+ ev.MouseY = last_y
+ default:
+ ev.Type = EventNone
+ }
+ if ev.Type != EventNone {
+ input_comm <- ev
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/nsf/termbox-go/terminfo.go b/vendor/github.com/nsf/termbox-go/terminfo.go
new file mode 100644
index 0000000..5d38fce
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/terminfo.go
@@ -0,0 +1,226 @@
+// +build !windows
+// This file contains a simple and incomplete implementation of the terminfo
+// database. Information was taken from the ncurses manpages term(5) and
+// terminfo(5). Currently, only the string capabilities for special keys and for
+// functions without parameters are actually used. Colors are still done with
+// ANSI escape sequences. Other special features that are not (yet?) supported
+// are reading from ~/.terminfo, the TERMINFO_DIRS variable, Berkeley database
+// format and extended capabilities.
+
+package termbox
+
+import (
+ "bytes"
+ "encoding/binary"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "strings"
+)
+
+const (
+ ti_magic = 0432
+ ti_header_length = 12
+ ti_mouse_enter = "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h"
+ ti_mouse_leave = "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l"
+)
+
+func load_terminfo() ([]byte, error) {
+ var data []byte
+ var err error
+
+ term := os.Getenv("TERM")
+ if term == "" {
+ return nil, fmt.Errorf("termbox: TERM not set")
+ }
+
+ // The following behaviour follows the one described in terminfo(5) as
+ // distributed by ncurses.
+
+ terminfo := os.Getenv("TERMINFO")
+ if terminfo != "" {
+ // if TERMINFO is set, no other directory should be searched
+ return ti_try_path(terminfo)
+ }
+
+ // next, consider ~/.terminfo
+ home := os.Getenv("HOME")
+ if home != "" {
+ data, err = ti_try_path(home + "/.terminfo")
+ if err == nil {
+ return data, nil
+ }
+ }
+
+ // next, TERMINFO_DIRS
+ dirs := os.Getenv("TERMINFO_DIRS")
+ if dirs != "" {
+ for _, dir := range strings.Split(dirs, ":") {
+ if dir == "" {
+ // "" -> "/usr/share/terminfo"
+ dir = "/usr/share/terminfo"
+ }
+ data, err = ti_try_path(dir)
+ if err == nil {
+ return data, nil
+ }
+ }
+ }
+
+ // fall back to /usr/share/terminfo
+ return ti_try_path("/usr/share/terminfo")
+}
+
+func ti_try_path(path string) (data []byte, err error) {
+ // load_terminfo already made sure it is set
+ term := os.Getenv("TERM")
+
+ // first try, the typical *nix path
+ terminfo := path + "/" + term[0:1] + "/" + term
+ data, err = ioutil.ReadFile(terminfo)
+ if err == nil {
+ return
+ }
+
+ // fallback to darwin specific dirs structure
+ terminfo = path + "/" + hex.EncodeToString([]byte(term[:1])) + "/" + term
+ data, err = ioutil.ReadFile(terminfo)
+ return
+}
+
+func setup_term_builtin() error {
+ name := os.Getenv("TERM")
+ if name == "" {
+ return errors.New("termbox: TERM environment variable not set")
+ }
+
+ for _, t := range terms {
+ if t.name == name {
+ keys = t.keys
+ funcs = t.funcs
+ return nil
+ }
+ }
+
+ compat_table := []struct {
+ partial string
+ keys []string
+ funcs []string
+ }{
+ {"xterm", xterm_keys, xterm_funcs},
+ {"rxvt", rxvt_unicode_keys, rxvt_unicode_funcs},
+ {"linux", linux_keys, linux_funcs},
+ {"Eterm", eterm_keys, eterm_funcs},
+ {"screen", screen_keys, screen_funcs},
+ // let's assume that 'cygwin' is xterm compatible
+ {"cygwin", xterm_keys, xterm_funcs},
+ {"st", xterm_keys, xterm_funcs},
+ }
+
+ // try compatibility variants
+ for _, it := range compat_table {
+ if strings.Contains(name, it.partial) {
+ keys = it.keys
+ funcs = it.funcs
+ return nil
+ }
+ }
+
+ return errors.New("termbox: unsupported terminal")
+}
+
+func setup_term() (err error) {
+ var data []byte
+ var header [6]int16
+ var str_offset, table_offset int16
+
+ data, err = load_terminfo()
+ if err != nil {
+ return setup_term_builtin()
+ }
+
+ rd := bytes.NewReader(data)
+ // 0: magic number, 1: size of names section, 2: size of boolean section, 3:
+ // size of numbers section (in integers), 4: size of the strings section (in
+ // integers), 5: size of the string table
+
+ err = binary.Read(rd, binary.LittleEndian, header[:])
+ if err != nil {
+ return
+ }
+
+ number_sec_len := int16(2)
+ if header[0] == 542 { // doc says it should be octal 0542, but what I see it terminfo files is 542, learn to program please... thank you..
+ number_sec_len = 4
+ }
+
+ if (header[1]+header[2])%2 != 0 {
+ // old quirk to align everything on word boundaries
+ header[2] += 1
+ }
+ str_offset = ti_header_length + header[1] + header[2] + number_sec_len*header[3]
+ table_offset = str_offset + 2*header[4]
+
+ keys = make([]string, 0xFFFF-key_min)
+ for i, _ := range keys {
+ keys[i], err = ti_read_string(rd, str_offset+2*ti_keys[i], table_offset)
+ if err != nil {
+ return
+ }
+ }
+ funcs = make([]string, t_max_funcs)
+ // the last two entries are reserved for mouse. because the table offset is
+ // not there, the two entries have to fill in manually
+ for i, _ := range funcs[:len(funcs)-2] {
+ funcs[i], err = ti_read_string(rd, str_offset+2*ti_funcs[i], table_offset)
+ if err != nil {
+ return
+ }
+ }
+ funcs[t_max_funcs-2] = ti_mouse_enter
+ funcs[t_max_funcs-1] = ti_mouse_leave
+ return nil
+}
+
+func ti_read_string(rd *bytes.Reader, str_off, table int16) (string, error) {
+ var off int16
+
+ _, err := rd.Seek(int64(str_off), 0)
+ if err != nil {
+ return "", err
+ }
+ err = binary.Read(rd, binary.LittleEndian, &off)
+ if err != nil {
+ return "", err
+ }
+ _, err = rd.Seek(int64(table+off), 0)
+ if err != nil {
+ return "", err
+ }
+ var bs []byte
+ for {
+ b, err := rd.ReadByte()
+ if err != nil {
+ return "", err
+ }
+ if b == byte(0x00) {
+ break
+ }
+ bs = append(bs, b)
+ }
+ return string(bs), nil
+}
+
+// "Maps" the function constants from termbox.go to the number of the respective
+// string capability in the terminfo file. Taken from (ncurses) term.h.
+var ti_funcs = []int16{
+ 28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88,
+}
+
+// Same as above for the special keys.
+var ti_keys = []int16{
+ 66, 68 /* apparently not a typo; 67 is F10 for whatever reason */, 69, 70,
+ 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, 81, 87, 61, 79, 83,
+}
diff --git a/vendor/github.com/nsf/termbox-go/terminfo_builtin.go b/vendor/github.com/nsf/termbox-go/terminfo_builtin.go
new file mode 100644
index 0000000..a948660
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/terminfo_builtin.go
@@ -0,0 +1,64 @@
+// +build !windows
+
+package termbox
+
+// Eterm
+var eterm_keys = []string{
+ "\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C",
+}
+var eterm_funcs = []string{
+ "\x1b7\x1b[?47h", "\x1b[2J\x1b[?47l\x1b8", "\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b[m\x0f", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "", "", "", "",
+}
+
+// screen
+var screen_keys = []string{
+ "\x1bOP", "\x1bOQ", "\x1bOR", "\x1bOS", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[1~", "\x1b[4~", "\x1b[5~", "\x1b[6~", "\x1bOA", "\x1bOB", "\x1bOD", "\x1bOC",
+}
+var screen_funcs = []string{
+ "\x1b[?1049h", "\x1b[?1049l", "\x1b[34h\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[J", "\x1b[m\x0f", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b[?1h\x1b=", "\x1b[?1l\x1b>", ti_mouse_enter, ti_mouse_leave,
+}
+
+// xterm
+var xterm_keys = []string{
+ "\x1bOP", "\x1bOQ", "\x1bOR", "\x1bOS", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1bOH", "\x1bOF", "\x1b[5~", "\x1b[6~", "\x1bOA", "\x1bOB", "\x1bOD", "\x1bOC",
+}
+var xterm_funcs = []string{
+ "\x1b[?1049h", "\x1b[?1049l", "\x1b[?12l\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b(B\x1b[m", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b[?1h\x1b=", "\x1b[?1l\x1b>", ti_mouse_enter, ti_mouse_leave,
+}
+
+// rxvt-unicode
+var rxvt_unicode_keys = []string{
+ "\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C",
+}
+var rxvt_unicode_funcs = []string{
+ "\x1b[?1049h", "\x1b[r\x1b[?1049l", "\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b[m\x1b(B", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b=", "\x1b>", ti_mouse_enter, ti_mouse_leave,
+}
+
+// linux
+var linux_keys = []string{
+ "\x1b[[A", "\x1b[[B", "\x1b[[C", "\x1b[[D", "\x1b[[E", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[1~", "\x1b[4~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C",
+}
+var linux_funcs = []string{
+ "", "", "\x1b[?25h\x1b[?0c", "\x1b[?25l\x1b[?1c", "\x1b[H\x1b[J", "\x1b[0;10m", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "", "", "", "",
+}
+
+// rxvt-256color
+var rxvt_256color_keys = []string{
+ "\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C",
+}
+var rxvt_256color_funcs = []string{
+ "\x1b7\x1b[?47h", "\x1b[2J\x1b[?47l\x1b8", "\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b[m\x0f", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b=", "\x1b>", ti_mouse_enter, ti_mouse_leave,
+}
+
+var terms = []struct {
+ name string
+ keys []string
+ funcs []string
+}{
+ {"Eterm", eterm_keys, eterm_funcs},
+ {"screen", screen_keys, screen_funcs},
+ {"xterm", xterm_keys, xterm_funcs},
+ {"rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs},
+ {"linux", linux_keys, linux_funcs},
+ {"rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs},
+}
diff --git a/vendor/go4.org/.gitignore b/vendor/go4.org/.gitignore
new file mode 100644
index 0000000..daf913b
--- /dev/null
+++ b/vendor/go4.org/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/go4.org/.travis.yml b/vendor/go4.org/.travis.yml
new file mode 100644
index 0000000..d9e211d
--- /dev/null
+++ b/vendor/go4.org/.travis.yml
@@ -0,0 +1,10 @@
+go_import_path: go4.org
+language: go
+go:
+ - "1.10.x"
+ - tip
+before_install:
+ - go get -u cloud.google.com/go/storage
+ - cd $HOME/gopath/src/cloud.google.com/go
+ - git reset --hard 4d445121f7d55f37b70187e796b16e64f6eea7a0
+ - cd $HOME/gopath/src/go4.org
diff --git a/vendor/go4.org/AUTHORS b/vendor/go4.org/AUTHORS
new file mode 100644
index 0000000..d1ad485
--- /dev/null
+++ b/vendor/go4.org/AUTHORS
@@ -0,0 +1,8 @@
+# This is the official list of go4 authors for copyright purposes.
+# This is distinct from the CONTRIBUTORS file, which is the list of
+# people who have contributed, even if they don't own the copyright on
+# their work.
+
+Mathieu Lonjaret
+Daniel Theophanes
+Google
diff --git a/vendor/go4.org/LICENSE b/vendor/go4.org/LICENSE
new file mode 100644
index 0000000..8f71f43
--- /dev/null
+++ b/vendor/go4.org/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/vendor/go4.org/README.md b/vendor/go4.org/README.md
new file mode 100644
index 0000000..677ec17
--- /dev/null
+++ b/vendor/go4.org/README.md
@@ -0,0 +1,57 @@
+# go4
+
+[![travis badge](https://travis-ci.org/camlistore/go4.svg?branch=master)](https://travis-ci.org/camlistore/go4 "Travis CI")
+
+[go4.org](http://go4.org) is a collection of packages for
+Go programmers.
+
+They started out living in [Perkeep](https://perkeep.org)'s repo
+and elsewhere but they have nothing to do with Perkeep, so we're
+moving them here.
+
+## Details
+
+* **single repo**. go4 is a single repo. That means things can be
+ changed and rearranged globally atomically with ease and
+ confidence.
+
+* **no backwards compatibility**. go4 makes no backwards compatibility
+ promises. If you want to use go4, vendor it. And next time you
+ update your vendor tree, update to the latest API if things in go4
+ changed. The plan is to eventually provide tools to make this
+ easier.
+
+* **forward progress** because we have no backwards compatibility,
+ it's always okay to change things to make things better. That also
+ means the bar for contributions is lower. We don't have to get the
+ API 100% correct in the first commit.
+
+* **no Go version policy** go4 packages are usually built and tested
+ with the latest Go stable version. However, go4 has no overarching
+ version policy; each package can declare its own set of supported
+ Go versions.
+
+* **code review** contributions must be code-reviewed. We're trying
+ out Gerrithub, to see if we can find a mix of Github Pull Requests
+ and Gerrit that works well for many people. We'll see.
+
+* **CLA compliant** contributors must agree to the Google CLA (the
+ same as Go itself). This ensures we can move things into Go as
+ necessary in the future. It also makes lawyers at various
+ companies happy. The CLA is **not** a copyright *assignment*; you
+ retain the copyright on your work. The CLA just says that your
+ work is open source and you have permission to open source it. See
+ https://golang.org/doc/contribute.html#cla
+
+* **docs, tests, portability** all code should be documented in the
+ normal Go style, have tests, and be portable to different
+ operating systems and architectures. We'll try to get builders in
+ place to help run the tests on different OS/arches. For now we
+ have Travis at least.
+
+## Contact
+
+For any question, or communication when a Github issue is not appropriate,
+please contact the [Perkeep mailing
+list](https://groups.google.com/forum/#!forum/perkeep).
+
diff --git a/vendor/go4.org/bytereplacer/bytereplacer.go b/vendor/go4.org/bytereplacer/bytereplacer.go
new file mode 100644
index 0000000..5daa379
--- /dev/null
+++ b/vendor/go4.org/bytereplacer/bytereplacer.go
@@ -0,0 +1,286 @@
+/*
+Copyright 2015 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package bytereplacer provides a utility for replacing parts of byte slices.
+package bytereplacer // import "go4.org/bytereplacer"
+
+import "bytes"
+
+// Replacer replaces a list of strings with replacements.
+// It is safe for concurrent use by multiple goroutines.
+type Replacer struct {
+ r replacer
+}
+
+// replacer is the interface that a replacement algorithm needs to implement.
+type replacer interface {
+ // Replace performs all replacements, in-place if possible.
+ Replace(s []byte) []byte
+}
+
+// New returns a new Replacer from a list of old, new string pairs.
+// Replacements are performed in order, without overlapping matches.
+func New(oldnew ...string) *Replacer {
+ if len(oldnew)%2 == 1 {
+ panic("bytes.NewReplacer: odd argument count")
+ }
+
+ allNewBytes := true
+ for i := 0; i < len(oldnew); i += 2 {
+ if len(oldnew[i]) != 1 {
+ return &Replacer{r: makeGenericReplacer(oldnew)}
+ }
+ if len(oldnew[i+1]) != 1 {
+ allNewBytes = false
+ }
+ }
+
+ if allNewBytes {
+ r := byteReplacer{}
+ for i := range r {
+ r[i] = byte(i)
+ }
+ // The first occurrence of old->new map takes precedence
+ // over the others with the same old string.
+ for i := len(oldnew) - 2; i >= 0; i -= 2 {
+ o := oldnew[i][0]
+ n := oldnew[i+1][0]
+ r[o] = n
+ }
+ return &Replacer{r: &r}
+ }
+
+ return &Replacer{r: makeGenericReplacer(oldnew)}
+}
+
+// Replace performs all replacements in-place on s. If the capacity
+// of s is not sufficient, a new slice is allocated, otherwise Replace
+// returns s.
+func (r *Replacer) Replace(s []byte) []byte {
+ return r.r.Replace(s)
+}
+
+type trieNode struct {
+ value []byte
+ priority int
+ prefix []byte
+ next *trieNode
+ table []*trieNode
+}
+
+func (t *trieNode) add(key, val []byte, priority int, r *genericReplacer) {
+ if len(key) == 0 {
+ if t.priority == 0 {
+ t.value = val
+ t.priority = priority
+ }
+ return
+ }
+
+ if len(t.prefix) > 0 {
+ // Need to split the prefix among multiple nodes.
+ var n int // length of the longest common prefix
+ for ; n < len(t.prefix) && n < len(key); n++ {
+ if t.prefix[n] != key[n] {
+ break
+ }
+ }
+ if n == len(t.prefix) {
+ t.next.add(key[n:], val, priority, r)
+ } else if n == 0 {
+ // First byte differs, start a new lookup table here. Looking up
+ // what is currently t.prefix[0] will lead to prefixNode, and
+ // looking up key[0] will lead to keyNode.
+ var prefixNode *trieNode
+ if len(t.prefix) == 1 {
+ prefixNode = t.next
+ } else {
+ prefixNode = &trieNode{
+ prefix: t.prefix[1:],
+ next: t.next,
+ }
+ }
+ keyNode := new(trieNode)
+ t.table = make([]*trieNode, r.tableSize)
+ t.table[r.mapping[t.prefix[0]]] = prefixNode
+ t.table[r.mapping[key[0]]] = keyNode
+ t.prefix = nil
+ t.next = nil
+ keyNode.add(key[1:], val, priority, r)
+ } else {
+ // Insert new node after the common section of the prefix.
+ next := &trieNode{
+ prefix: t.prefix[n:],
+ next: t.next,
+ }
+ t.prefix = t.prefix[:n]
+ t.next = next
+ next.add(key[n:], val, priority, r)
+ }
+ } else if t.table != nil {
+ // Insert into existing table.
+ m := r.mapping[key[0]]
+ if t.table[m] == nil {
+ t.table[m] = new(trieNode)
+ }
+ t.table[m].add(key[1:], val, priority, r)
+ } else {
+ t.prefix = key
+ t.next = new(trieNode)
+ t.next.add(nil, val, priority, r)
+ }
+}
+
+func (r *genericReplacer) lookup(s []byte, ignoreRoot bool) (val []byte, keylen int, found bool) {
+ // Iterate down the trie to the end, and grab the value and keylen with
+ // the highest priority.
+ bestPriority := 0
+ node := &r.root
+ n := 0
+ for node != nil {
+ if node.priority > bestPriority && !(ignoreRoot && node == &r.root) {
+ bestPriority = node.priority
+ val = node.value
+ keylen = n
+ found = true
+ }
+
+ if len(s) == 0 {
+ break
+ }
+ if node.table != nil {
+ index := r.mapping[s[0]]
+ if int(index) == r.tableSize {
+ break
+ }
+ node = node.table[index]
+ s = s[1:]
+ n++
+ } else if len(node.prefix) > 0 && bytes.HasPrefix(s, node.prefix) {
+ n += len(node.prefix)
+ s = s[len(node.prefix):]
+ node = node.next
+ } else {
+ break
+ }
+ }
+ return
+}
+
+// genericReplacer is the fully generic algorithm.
+// It's used as a fallback when nothing faster can be used.
+type genericReplacer struct {
+ root trieNode
+ // tableSize is the size of a trie node's lookup table. It is the number
+ // of unique key bytes.
+ tableSize int
+ // mapping maps from key bytes to a dense index for trieNode.table.
+ mapping [256]byte
+}
+
+func makeGenericReplacer(oldnew []string) *genericReplacer {
+ r := new(genericReplacer)
+ // Find each byte used, then assign them each an index.
+ for i := 0; i < len(oldnew); i += 2 {
+ key := oldnew[i]
+ for j := 0; j < len(key); j++ {
+ r.mapping[key[j]] = 1
+ }
+ }
+
+ for _, b := range r.mapping {
+ r.tableSize += int(b)
+ }
+
+ var index byte
+ for i, b := range r.mapping {
+ if b == 0 {
+ r.mapping[i] = byte(r.tableSize)
+ } else {
+ r.mapping[i] = index
+ index++
+ }
+ }
+ // Ensure root node uses a lookup table (for performance).
+ r.root.table = make([]*trieNode, r.tableSize)
+
+ for i := 0; i < len(oldnew); i += 2 {
+ r.root.add([]byte(oldnew[i]), []byte(oldnew[i+1]), len(oldnew)-i, r)
+ }
+ return r
+}
+
+func (r *genericReplacer) Replace(s []byte) []byte {
+ var last int
+ var prevMatchEmpty bool
+ dst := s[:0]
+ grown := false
+ for i := 0; i <= len(s); {
+ // Fast path: s[i] is not a prefix of any pattern.
+ if i != len(s) && r.root.priority == 0 {
+ index := int(r.mapping[s[i]])
+ if index == r.tableSize || r.root.table[index] == nil {
+ i++
+ continue
+ }
+ }
+
+ // Ignore the empty match iff the previous loop found the empty match.
+ val, keylen, match := r.lookup(s[i:], prevMatchEmpty)
+ prevMatchEmpty = match && keylen == 0
+ if match {
+ dst = append(dst, s[last:i]...)
+ if diff := len(val) - keylen; grown || diff < 0 {
+ dst = append(dst, val...)
+ i += keylen
+ } else if diff <= cap(s)-len(s) {
+ // The replacement is larger than the original, but can still fit in the original buffer.
+ copy(s[i+len(val):cap(dst)], s[i+keylen:])
+ dst = append(dst, val...)
+ s = s[:len(s)+diff]
+ i += len(val)
+ } else {
+ // The output will grow larger than the original buffer. Allocate a new one.
+ grown = true
+ newDst := make([]byte, len(dst), cap(dst)+diff)
+ copy(newDst, dst)
+ dst = newDst
+
+ dst = append(dst, val...)
+ i += keylen
+ }
+ last = i
+ continue
+ }
+ i++
+ }
+ if last != len(s) {
+ dst = append(dst, s[last:]...)
+ }
+ return dst
+}
+
+// byteReplacer is the implementation that's used when all the "old"
+// and "new" values are single ASCII bytes.
+// The array contains replacement bytes indexed by old byte.
+type byteReplacer [256]byte
+
+func (r *byteReplacer) Replace(s []byte) []byte {
+ for i, b := range s {
+ s[i] = r[b]
+ }
+ return s
+}
diff --git a/vendor/go4.org/bytereplacer/bytereplacer_test.go b/vendor/go4.org/bytereplacer/bytereplacer_test.go
new file mode 100644
index 0000000..81e8c6f
--- /dev/null
+++ b/vendor/go4.org/bytereplacer/bytereplacer_test.go
@@ -0,0 +1,423 @@
+/*
+Copyright 2015 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package bytereplacer
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+)
+
+var htmlEscaper = New(
+ "&", "&",
+ "<", "<",
+ ">", ">",
+ `"`, """,
+ "'", "'",
+)
+
+var htmlUnescaper = New(
+ "&", "&",
+ "<", "<",
+ ">", ">",
+ """, `"`,
+ "'", "'",
+)
+
+var capitalLetters = New("a", "A", "b", "B")
+
+func TestReplacer(t *testing.T) {
+ type testCase struct {
+ r *Replacer
+ in, out string
+ }
+ var testCases []testCase
+
+ // str converts 0xff to "\xff". This isn't just string(b) since that converts to UTF-8.
+ str := func(b byte) string {
+ return string([]byte{b})
+ }
+ var s []string
+
+ // inc maps "\x00"->"\x01", ..., "a"->"b", "b"->"c", ..., "\xff"->"\x00".
+ s = nil
+ for i := 0; i < 256; i++ {
+ s = append(s, str(byte(i)), str(byte(i+1)))
+ }
+ inc := New(s...)
+
+ // Test cases with 1-byte old strings, 1-byte new strings.
+ testCases = append(testCases,
+ testCase{capitalLetters, "brad", "BrAd"},
+ testCase{capitalLetters, strings.Repeat("a", (32<<10)+123), strings.Repeat("A", (32<<10)+123)},
+ testCase{capitalLetters, "", ""},
+
+ testCase{inc, "brad", "csbe"},
+ testCase{inc, "\x00\xff", "\x01\x00"},
+ testCase{inc, "", ""},
+
+ testCase{New("a", "1", "a", "2"), "brad", "br1d"},
+ )
+
+ // repeat maps "a"->"a", "b"->"bb", "c"->"ccc", ...
+ s = nil
+ for i := 0; i < 256; i++ {
+ n := i + 1 - 'a'
+ if n < 1 {
+ n = 1
+ }
+ s = append(s, str(byte(i)), strings.Repeat(str(byte(i)), n))
+ }
+ repeat := New(s...)
+
+ // Test cases with 1-byte old strings, variable length new strings.
+ testCases = append(testCases,
+ testCase{htmlEscaper, "No changes", "No changes"},
+ testCase{htmlEscaper, "I <3 escaping & stuff", "I <3 escaping & stuff"},
+ testCase{htmlEscaper, "&&&", "&&&"},
+ testCase{htmlEscaper, "", ""},
+
+ testCase{repeat, "brad", "bbrrrrrrrrrrrrrrrrrradddd"},
+ testCase{repeat, "abba", "abbbba"},
+ testCase{repeat, "", ""},
+
+ testCase{New("a", "11", "a", "22"), "brad", "br11d"},
+ )
+
+ // The remaining test cases have variable length old strings.
+
+ testCases = append(testCases,
+ testCase{htmlUnescaper, "&", "&"},
+ testCase{htmlUnescaper, "<b>HTML's neat</b>", "HTML's neat"},
+ testCase{htmlUnescaper, "", ""},
+
+ testCase{New("a", "1", "a", "2", "xxx", "xxx"), "brad", "br1d"},
+
+ testCase{New("a", "1", "aa", "2", "aaa", "3"), "aaaa", "1111"},
+
+ testCase{New("aaa", "3", "aa", "2", "a", "1"), "aaaa", "31"},
+ )
+
+ // gen1 has multiple old strings of variable length. There is no
+ // overall non-empty common prefix, but some pairwise common prefixes.
+ gen1 := New(
+ "aaa", "3[aaa]",
+ "aa", "2[aa]",
+ "a", "1[a]",
+ "i", "i",
+ "longerst", "most long",
+ "longer", "medium",
+ "long", "short",
+ "xx", "xx",
+ "x", "X",
+ "X", "Y",
+ "Y", "Z",
+ )
+ testCases = append(testCases,
+ testCase{gen1, "fooaaabar", "foo3[aaa]b1[a]r"},
+ testCase{gen1, "long, longerst, longer", "short, most long, medium"},
+ testCase{gen1, "xxxxx", "xxxxX"},
+ testCase{gen1, "XiX", "YiY"},
+ testCase{gen1, "", ""},
+ )
+
+ // gen2 has multiple old strings with no pairwise common prefix.
+ gen2 := New(
+ "roses", "red",
+ "violets", "blue",
+ "sugar", "sweet",
+ )
+ testCases = append(testCases,
+ testCase{gen2, "roses are red, violets are blue...", "red are red, blue are blue..."},
+ testCase{gen2, "", ""},
+ )
+
+ // gen3 has multiple old strings with an overall common prefix.
+ gen3 := New(
+ "abracadabra", "poof",
+ "abracadabrakazam", "splat",
+ "abraham", "lincoln",
+ "abrasion", "scrape",
+ "abraham", "isaac",
+ )
+ testCases = append(testCases,
+ testCase{gen3, "abracadabrakazam abraham", "poofkazam lincoln"},
+ testCase{gen3, "abrasion abracad", "scrape abracad"},
+ testCase{gen3, "abba abram abrasive", "abba abram abrasive"},
+ testCase{gen3, "", ""},
+ )
+
+ // foo{1,2,3,4} have multiple old strings with an overall common prefix
+ // and 1- or 2- byte extensions from the common prefix.
+ foo1 := New(
+ "foo1", "A",
+ "foo2", "B",
+ "foo3", "C",
+ )
+ foo2 := New(
+ "foo1", "A",
+ "foo2", "B",
+ "foo31", "C",
+ "foo32", "D",
+ )
+ foo3 := New(
+ "foo11", "A",
+ "foo12", "B",
+ "foo31", "C",
+ "foo32", "D",
+ )
+ foo4 := New(
+ "foo12", "B",
+ "foo32", "D",
+ )
+ testCases = append(testCases,
+ testCase{foo1, "fofoofoo12foo32oo", "fofooA2C2oo"},
+ testCase{foo1, "", ""},
+
+ testCase{foo2, "fofoofoo12foo32oo", "fofooA2Doo"},
+ testCase{foo2, "", ""},
+
+ testCase{foo3, "fofoofoo12foo32oo", "fofooBDoo"},
+ testCase{foo3, "", ""},
+
+ testCase{foo4, "fofoofoo12foo32oo", "fofooBDoo"},
+ testCase{foo4, "", ""},
+ )
+
+ // genAll maps "\x00\x01\x02...\xfe\xff" to "[all]", amongst other things.
+ allBytes := make([]byte, 256)
+ for i := range allBytes {
+ allBytes[i] = byte(i)
+ }
+ allString := string(allBytes)
+ genAll := New(
+ allString, "[all]",
+ "\xff", "[ff]",
+ "\x00", "[00]",
+ )
+ testCases = append(testCases,
+ testCase{genAll, allString, "[all]"},
+ testCase{genAll, "a\xff" + allString + "\x00", "a[ff][all][00]"},
+ testCase{genAll, "", ""},
+ )
+
+ // Test cases with empty old strings.
+
+ blankToX1 := New("", "X")
+ blankToX2 := New("", "X", "", "")
+ blankHighPriority := New("", "X", "o", "O")
+ blankLowPriority := New("o", "O", "", "X")
+ blankNoOp1 := New("", "")
+ blankNoOp2 := New("", "", "", "A")
+ blankFoo := New("", "X", "foobar", "R", "foobaz", "Z")
+ testCases = append(testCases,
+ testCase{blankToX1, "foo", "XfXoXoX"},
+ testCase{blankToX1, "", "X"},
+
+ testCase{blankToX2, "foo", "XfXoXoX"},
+ testCase{blankToX2, "", "X"},
+
+ testCase{blankHighPriority, "oo", "XOXOX"},
+ testCase{blankHighPriority, "ii", "XiXiX"},
+ testCase{blankHighPriority, "oiio", "XOXiXiXOX"},
+ testCase{blankHighPriority, "iooi", "XiXOXOXiX"},
+ testCase{blankHighPriority, "", "X"},
+
+ testCase{blankLowPriority, "oo", "OOX"},
+ testCase{blankLowPriority, "ii", "XiXiX"},
+ testCase{blankLowPriority, "oiio", "OXiXiOX"},
+ testCase{blankLowPriority, "iooi", "XiOOXiX"},
+ testCase{blankLowPriority, "", "X"},
+
+ testCase{blankNoOp1, "foo", "foo"},
+ testCase{blankNoOp1, "", ""},
+
+ testCase{blankNoOp2, "foo", "foo"},
+ testCase{blankNoOp2, "", ""},
+
+ testCase{blankFoo, "foobarfoobaz", "XRXZX"},
+ testCase{blankFoo, "foobar-foobaz", "XRX-XZX"},
+ testCase{blankFoo, "", "X"},
+ )
+
+ // single string replacer
+
+ abcMatcher := New("abc", "[match]")
+
+ testCases = append(testCases,
+ testCase{abcMatcher, "", ""},
+ testCase{abcMatcher, "ab", "ab"},
+ testCase{abcMatcher, "abc", "[match]"},
+ testCase{abcMatcher, "abcd", "[match]d"},
+ testCase{abcMatcher, "cabcabcdabca", "c[match][match]d[match]a"},
+ )
+
+ // Issue 6659 cases (more single string replacer)
+
+ noHello := New("Hello", "")
+ testCases = append(testCases,
+ testCase{noHello, "Hello", ""},
+ testCase{noHello, "Hellox", "x"},
+ testCase{noHello, "xHello", "x"},
+ testCase{noHello, "xHellox", "xx"},
+ )
+
+ // No-arg test cases.
+
+ nop := New()
+ testCases = append(testCases,
+ testCase{nop, "abc", "abc"},
+ testCase{nop, "", ""},
+ )
+
+ // Run the test cases.
+
+ for i, tc := range testCases {
+ {
+ // Replace with len(in) == cap(in)
+ in := make([]byte, len(tc.in))
+ copy(in, tc.in)
+ if s := string(tc.r.Replace(in)); s != tc.out {
+ t.Errorf("%d. Replace(%q /* len == cap */) = %q, want %q", i, tc.in, s, tc.out)
+ }
+ }
+
+ {
+ // Replace with len(in) < cap(in)
+ in := make([]byte, len(tc.in), len(tc.in)*2)
+ copy(in, tc.in)
+ if s := string(tc.r.Replace(in)); s != tc.out {
+ t.Errorf("%d. Replace(%q /* len < cap */) = %q, want %q", i, tc.in, s, tc.out)
+ }
+ }
+ }
+}
+
+func BenchmarkGenericNoMatch(b *testing.B) {
+ str := []byte(strings.Repeat("A", 100) + strings.Repeat("B", 100))
+ generic := New("a", "A", "b", "B", "12", "123") // varying lengths forces generic
+ for i := 0; i < b.N; i++ {
+ generic.Replace(str)
+ }
+}
+
+func BenchmarkGenericMatch1(b *testing.B) {
+ str := []byte(strings.Repeat("a", 100) + strings.Repeat("b", 100))
+ generic := New("a", "A", "b", "B", "12", "123")
+ for i := 0; i < b.N; i++ {
+ generic.Replace(str)
+ }
+}
+
+func BenchmarkGenericMatch2(b *testing.B) {
+ str := bytes.Repeat([]byte("It's <b>HTML</b>!"), 100)
+ for i := 0; i < b.N; i++ {
+ htmlUnescaper.Replace(str)
+ }
+}
+
+func benchmarkSingleString(b *testing.B, pattern, text string) {
+ r := New(pattern, "[match]")
+ buf := make([]byte, len(text), len(text)*7)
+ b.SetBytes(int64(len(text)))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ copy(buf, text)
+ r.Replace(buf)
+ }
+}
+
+func BenchmarkSingleMaxSkipping(b *testing.B) {
+ benchmarkSingleString(b, strings.Repeat("b", 25), strings.Repeat("a", 10000))
+}
+
+func BenchmarkSingleLongSuffixFail(b *testing.B) {
+ benchmarkSingleString(b, "b"+strings.Repeat("a", 500), strings.Repeat("a", 1002))
+}
+
+func BenchmarkSingleMatch(b *testing.B) {
+ benchmarkSingleString(b, "abcdef", strings.Repeat("abcdefghijklmno", 1000))
+}
+
+func benchmarkReplacer(b *testing.B, r *Replacer, str string) {
+ buf := make([]byte, len(str))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ copy(buf, str)
+ r.Replace(buf)
+ }
+}
+
+func BenchmarkByteByteNoMatch(b *testing.B) {
+ benchmarkReplacer(b, capitalLetters, strings.Repeat("A", 100)+strings.Repeat("B", 100))
+}
+
+func BenchmarkByteByteMatch(b *testing.B) {
+ benchmarkReplacer(b, capitalLetters, strings.Repeat("a", 100)+strings.Repeat("b", 100))
+}
+
+func BenchmarkByteStringMatch(b *testing.B) {
+ benchmarkReplacer(b, htmlEscaper, "<"+strings.Repeat("a", 99)+strings.Repeat("b", 99)+">")
+}
+
+func BenchmarkHTMLEscapeNew(b *testing.B) {
+ benchmarkReplacer(b, htmlEscaper, "I <3 to escape HTML & other text too.")
+}
+
+func BenchmarkHTMLEscapeOld(b *testing.B) {
+ str := "I <3 to escape HTML & other text too."
+ buf := make([]byte, len(str))
+ for i := 0; i < b.N; i++ {
+ copy(buf, str)
+ oldHTMLEscape(buf)
+ }
+}
+
+// The http package's old HTML escaping function in bytes form.
+func oldHTMLEscape(s []byte) []byte {
+ s = bytes.Replace(s, []byte("&"), []byte("&"), -1)
+ s = bytes.Replace(s, []byte("<"), []byte("<"), -1)
+ s = bytes.Replace(s, []byte(">"), []byte(">"), -1)
+ s = bytes.Replace(s, []byte(`"`), []byte("""), -1)
+ s = bytes.Replace(s, []byte("'"), []byte("'"), -1)
+ return s
+}
+
+// BenchmarkByteByteReplaces compares byteByteImpl against multiple Replaces.
+func BenchmarkByteByteReplaces(b *testing.B) {
+ str := strings.Repeat("a", 100) + strings.Repeat("b", 100)
+ for i := 0; i < b.N; i++ {
+ bytes.Replace(bytes.Replace([]byte(str), []byte{'a'}, []byte{'A'}, -1), []byte{'b'}, []byte{'B'}, -1)
+ }
+}
+
+// BenchmarkByteByteMap compares byteByteImpl against Map.
+func BenchmarkByteByteMap(b *testing.B) {
+ str := strings.Repeat("a", 100) + strings.Repeat("b", 100)
+ fn := func(r rune) rune {
+ switch r {
+ case 'a':
+ return 'A'
+ case 'b':
+ return 'B'
+ }
+ return r
+ }
+ for i := 0; i < b.N; i++ {
+ bytes.Map(fn, []byte(str))
+ }
+}
diff --git a/vendor/go4.org/cloud/cloudlaunch/cloudlaunch.go b/vendor/go4.org/cloud/cloudlaunch/cloudlaunch.go
new file mode 100644
index 0000000..b87cf81
--- /dev/null
+++ b/vendor/go4.org/cloud/cloudlaunch/cloudlaunch.go
@@ -0,0 +1,457 @@
+/*
+Copyright 2015 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package cloudlaunch helps binaries run themselves on The Cloud, copying
+// themselves to GCE.
+package cloudlaunch // import "go4.org/cloud/cloudlaunch"
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "path"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "time"
+
+ "go4.org/cloud/google/gceutil"
+
+ "cloud.google.com/go/compute/metadata"
+ "cloud.google.com/go/storage"
+ "golang.org/x/net/context"
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/google"
+ compute "google.golang.org/api/compute/v1"
+ "google.golang.org/api/googleapi"
+ "google.golang.org/api/option"
+ storageapi "google.golang.org/api/storage/v1"
+)
+
+func readFile(v string) string {
+ slurp, err := ioutil.ReadFile(v)
+ if err != nil {
+ log.Fatalf("Error reading %s: %v", v, err)
+ }
+ return strings.TrimSpace(string(slurp))
+}
+
+const baseConfig = `#cloud-config
+coreos:
+ update:
+ group: stable
+ reboot-strategy: $REBOOT
+ units:
+ - name: $NAME.service
+ command: start
+ content: |
+ [Unit]
+ Description=$NAME service
+ After=network.target
+
+ [Service]
+ Type=simple
+ ExecStartPre=/bin/sh -c 'mkdir -p /opt/bin && /usr/bin/curl --silent -f -o /opt/bin/$NAME $URL?$(date +%s) && chmod +x /opt/bin/$NAME'
+ ExecStart=/opt/bin/$NAME
+ RestartSec=10
+ Restart=always
+ StartLimitInterval=0
+
+ [Install]
+ WantedBy=network-online.target
+`
+
+// RestartPolicy controls whether the binary automatically restarts.
+type RestartPolicy int
+
+const (
+ RestartOnUpdates RestartPolicy = iota
+ RestartNever
+ // TODO: more graceful restarts; make systemd own listening on network sockets,
+ // don't break connections.
+)
+
+type Config struct {
+ // Name is the name of a service to run.
+ // This is the name of the systemd service (without .service)
+ // and the name of the GCE instance.
+ Name string
+
+ // RestartPolicy controls whether the binary automatically restarts
+ // on updates. The zero value means automatic.
+ RestartPolicy RestartPolicy
+
+ // UpdateStrategy sets the CoreOS automatic update strategy, and the
+ // associated reboots. Possible values are "best-effort", "etcd-lock",
+ // "reboot", "off", with "best-effort" being the default. See
+ // https://coreos.com/os/docs/latest/update-strategies.html
+ UpdateStrategy string
+
+ // BinaryBucket and BinaryObject are the GCS bucket and object
+ // within that bucket containing the Linux binary to download
+ // on boot and occasionally run. This binary must be public
+ // (at least for now).
+ BinaryBucket string
+ BinaryObject string // defaults to Name
+
+ GCEProjectID string
+ Zone string // defaults to us-central1-f
+ SSD bool
+
+ Scopes []string // any additional scopes
+
+ MachineType string
+ InstanceName string
+}
+
+// cloudLaunch is a launch of a Config.
+type cloudLaunch struct {
+ *Config
+ oauthClient *http.Client
+ computeService *compute.Service
+}
+
+func (c *Config) binaryURL() string {
+ return "https://storage.googleapis.com/" + c.BinaryBucket + "/" + c.binaryObject()
+}
+
+func (c *Config) instName() string { return c.Name } // for now
+func (c *Config) zone() string { return strDefault(c.Zone, "us-central1-f") }
+func (c *Config) machineType() string { return strDefault(c.MachineType, "g1-small") }
+func (c *Config) binaryObject() string { return strDefault(c.BinaryObject, c.Name) }
+func (c *Config) updateStrategy() string { return strDefault(c.UpdateStrategy, "best-effort") }
+
+func (c *Config) projectAPIURL() string {
+ return "https://www.googleapis.com/compute/v1/projects/" + c.GCEProjectID
+}
+func (c *Config) machineTypeURL() string {
+ return c.projectAPIURL() + "/zones/" + c.zone() + "/machineTypes/" + c.machineType()
+}
+
+func strDefault(a, b string) string {
+ if a != "" {
+ return a
+ }
+ return b
+}
+
+var (
+ doLaunch = flag.Bool("cloudlaunch", false, "Deploy or update this binary to the cloud. Must be on Linux, for now.")
+)
+
+func (c *Config) MaybeDeploy() {
+ flag.Parse()
+ if !*doLaunch {
+ go c.restartLoop()
+ return
+ }
+ defer os.Exit(1) // backup, in case we return without Fatal or os.Exit later
+
+ if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
+ log.Fatal("Can only use --cloudlaunch on linux/amd64, for now.")
+ }
+
+ if c.GCEProjectID == "" {
+ log.Fatal("cloudconfig.GCEProjectID is empty")
+ }
+ filename := filepath.Join(os.Getenv("HOME"), "keys", c.GCEProjectID+".key.json")
+ log.Printf("Using OAuth config from JSON service file: %s", filename)
+ jwtConf, err := google.JWTConfigFromJSON([]byte(readFile(filename)), append([]string{
+ storageapi.DevstorageFullControlScope,
+ compute.ComputeScope,
+ "https://www.googleapis.com/auth/cloud-platform",
+ }, c.Scopes...)...)
+ if err != nil {
+ log.Fatalf("ConfigFromJSON: %v", err)
+ }
+
+ cl := &cloudLaunch{
+ Config: c,
+ oauthClient: jwtConf.Client(oauth2.NoContext),
+ }
+ cl.computeService, _ = compute.New(cl.oauthClient)
+
+ cl.uploadBinary()
+ cl.createInstance()
+ os.Exit(0)
+}
+
+func (c *Config) restartLoop() {
+ if !metadata.OnGCE() {
+ return
+ }
+ if c.RestartPolicy == RestartNever {
+ return
+ }
+ url := c.binaryURL()
+ var lastEtag string
+ for {
+ res, err := http.Head(url + "?" + fmt.Sprint(time.Now().Unix()))
+ if err != nil {
+ log.Printf("Warning: %v", err)
+ time.Sleep(15 * time.Second)
+ continue
+ }
+ etag := res.Header.Get("Etag")
+ if etag == "" {
+ log.Printf("Warning, no ETag in response: %v", res)
+ time.Sleep(15 * time.Second)
+ continue
+ }
+ if lastEtag != "" && etag != lastEtag {
+ log.Printf("Binary updated; restarting.")
+ // TODO: more graceful restart, letting systemd own the network connections.
+ // Then we can finish up requests here.
+ os.Exit(0)
+ }
+ lastEtag = etag
+ time.Sleep(15 * time.Second)
+ }
+}
+
+// uploadBinary uploads the currently-running Linux binary.
+// It crashes if it fails.
+func (cl *cloudLaunch) uploadBinary() {
+ ctx := context.Background()
+ if cl.BinaryBucket == "" {
+ log.Fatal("cloudlaunch: Config.BinaryBucket is empty")
+ }
+ stoClient, err := storage.NewClient(ctx, option.WithHTTPClient(cl.oauthClient))
+ if err != nil {
+ log.Fatal(err)
+ }
+ w := stoClient.Bucket(cl.BinaryBucket).Object(cl.binaryObject()).NewWriter(ctx)
+ if err != nil {
+ log.Fatal(err)
+ }
+ w.ACL = []storage.ACLRule{
+ // If you don't give the owners access, the web UI seems to
+ // have a bug and doesn't have access to see that it's public, so
+ // won't render the "Shared Publicly" link. So we do that, even
+ // though it's dumb and unnecessary otherwise:
+ {
+ Entity: storage.ACLEntity("project-owners-" + cl.GCEProjectID),
+ Role: storage.RoleOwner,
+ },
+ // Public, so our systemd unit can get it easily:
+ {
+ Entity: storage.AllUsers,
+ Role: storage.RoleReader,
+ },
+ }
+ w.CacheControl = "no-cache"
+ selfPath := getSelfPath()
+ log.Printf("Uploading %q to %v", selfPath, cl.binaryURL())
+ f, err := os.Open(selfPath)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer f.Close()
+ n, err := io.Copy(w, f)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := w.Close(); err != nil {
+ log.Fatal(err)
+ }
+ log.Printf("Uploaded %d bytes", n)
+}
+
+func getSelfPath() string {
+ if runtime.GOOS != "linux" {
+ panic("TODO")
+ }
+ v, err := os.Readlink("/proc/self/exe")
+ if err != nil {
+ log.Fatal(err)
+ }
+ return v
+}
+
+func zoneInRegion(zone, regionURL string) bool {
+ if zone == "" {
+ panic("empty zone")
+ }
+ if regionURL == "" {
+ panic("empty regionURL")
+ }
+ // zone is like "us-central1-f"
+ // regionURL is like "https://www.googleapis.com/compute/v1/projects/camlistore-website/regions/us-central1"
+ region := path.Base(regionURL) // "us-central1"
+ if region == "" {
+ panic("empty region")
+ }
+ return strings.HasPrefix(zone, region)
+}
+
+// findIP finds an IP address to use, or returns the empty string if none is found.
+// It tries to find a reserved one in the same region where the name of the reserved IP
+// is "NAME-ip" and the IP is not in use.
+func (cl *cloudLaunch) findIP() string {
+ // Try to find it by name.
+ aggAddrList, err := cl.computeService.Addresses.AggregatedList(cl.GCEProjectID).Do()
+ if err != nil {
+ log.Fatal(err)
+ }
+ // https://godoc.org/google.golang.org/api/compute/v1#AddressAggregatedList
+ var ip string
+IPLoop:
+ for _, asl := range aggAddrList.Items {
+ for _, addr := range asl.Addresses {
+ log.Printf(" addr: %#v", addr)
+ if addr.Name == cl.Name+"-ip" && addr.Status == "RESERVED" && zoneInRegion(cl.zone(), addr.Region) {
+ ip = addr.Address
+ break IPLoop
+ }
+ }
+ }
+ return ip
+}
+
+func (cl *cloudLaunch) createInstance() {
+ inst := cl.lookupInstance()
+ if inst != nil {
+ log.Printf("Instance exists; not re-creating.")
+ return
+ }
+
+ log.Printf("Instance doesn't exist; creating...")
+
+ ip := cl.findIP()
+ log.Printf("Found IP: %v", ip)
+
+ cloudConfig := strings.NewReplacer(
+ "$NAME", cl.Name,
+ "$URL", cl.binaryURL(),
+ "$REBOOT", cl.updateStrategy(),
+ ).Replace(baseConfig)
+
+ instance := &compute.Instance{
+ Name: cl.instName(),
+ Description: cl.Name,
+ MachineType: cl.machineTypeURL(),
+ Disks: []*compute.AttachedDisk{cl.instanceDisk()},
+ Tags: &compute.Tags{
+ Items: []string{"http-server", "https-server"},
+ },
+ Metadata: &compute.Metadata{
+ Items: []*compute.MetadataItems{
+ {
+ Key: "user-data",
+ Value: googleapi.String(cloudConfig),
+ },
+ },
+ },
+ NetworkInterfaces: []*compute.NetworkInterface{
+ &compute.NetworkInterface{
+ AccessConfigs: []*compute.AccessConfig{
+ &compute.AccessConfig{
+ Type: "ONE_TO_ONE_NAT",
+ Name: "External NAT",
+ NatIP: ip,
+ },
+ },
+ Network: cl.projectAPIURL() + "/global/networks/default",
+ },
+ },
+ ServiceAccounts: []*compute.ServiceAccount{
+ {
+ Email: "default",
+ Scopes: cl.Scopes,
+ },
+ },
+ }
+
+ log.Printf("Creating instance...")
+ op, err := cl.computeService.Instances.Insert(cl.GCEProjectID, cl.zone(), instance).Do()
+ if err != nil {
+ log.Fatalf("Failed to create instance: %v", err)
+ }
+ opName := op.Name
+ log.Printf("Created. Waiting on operation %v", opName)
+OpLoop:
+ for {
+ time.Sleep(2 * time.Second)
+ op, err := cl.computeService.ZoneOperations.Get(cl.GCEProjectID, cl.zone(), opName).Do()
+ if err != nil {
+ log.Fatalf("Failed to get op %s: %v", opName, err)
+ }
+ switch op.Status {
+ case "PENDING", "RUNNING":
+ log.Printf("Waiting on operation %v", opName)
+ continue
+ case "DONE":
+ if op.Error != nil {
+ for _, operr := range op.Error.Errors {
+ log.Printf("Error: %+v", operr)
+ }
+ log.Fatalf("Failed to start.")
+ }
+ log.Printf("Success. %+v", op)
+ break OpLoop
+ default:
+ log.Fatalf("Unknown status %q: %+v", op.Status, op)
+ }
+ }
+
+ inst, err = cl.computeService.Instances.Get(cl.GCEProjectID, cl.zone(), cl.instName()).Do()
+ if err != nil {
+ log.Fatalf("Error getting instance after creation: %v", err)
+ }
+ ij, _ := json.MarshalIndent(inst, "", " ")
+ log.Printf("%s", ij)
+ log.Printf("Instance created.")
+ os.Exit(0)
+}
+
+// returns nil if instance doesn't exist.
+func (cl *cloudLaunch) lookupInstance() *compute.Instance {
+ inst, err := cl.computeService.Instances.Get(cl.GCEProjectID, cl.zone(), cl.instName()).Do()
+ if ae, ok := err.(*googleapi.Error); ok && ae.Code == 404 {
+ return nil
+ } else if err != nil {
+ log.Fatalf("Instances.Get: %v", err)
+ }
+ return inst
+}
+
+func (cl *cloudLaunch) instanceDisk() *compute.AttachedDisk {
+ imageURL, err := gceutil.CoreOSImageURL(cl.oauthClient)
+ if err != nil {
+ log.Fatalf("error looking up latest CoreOS stable image: %v", err)
+ }
+ diskName := cl.instName() + "-coreos-stateless-pd"
+ var diskType string
+ if cl.SSD {
+ diskType = cl.projectAPIURL() + "/zones/" + cl.zone() + "/diskTypes/pd-ssd"
+ }
+ return &compute.AttachedDisk{
+ AutoDelete: true,
+ Boot: true,
+ Type: "PERSISTENT",
+ InitializeParams: &compute.AttachedDiskInitializeParams{
+ DiskName: diskName,
+ SourceImage: imageURL,
+ DiskSizeGb: 50,
+ DiskType: diskType,
+ },
+ }
+}
diff --git a/vendor/go4.org/cloud/google/gceutil/gceutil.go b/vendor/go4.org/cloud/google/gceutil/gceutil.go
new file mode 100644
index 0000000..3ceaa98
--- /dev/null
+++ b/vendor/go4.org/cloud/google/gceutil/gceutil.go
@@ -0,0 +1,110 @@
+/*
+Copyright 2015 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package gceutil provides utility functions to help with instances on
+// Google Compute Engine.
+package gceutil // import "go4.org/cloud/google/gceutil"
+
+import (
+ "encoding/json"
+ "errors"
+ "net/http"
+ "strings"
+ "time"
+
+ "google.golang.org/api/compute/v1"
+)
+
+// CoreOSImageURL returns the URL of the latest stable CoreOS image for running on Google Compute Engine.
+func CoreOSImageURL(cl *http.Client) (string, error) {
+ resp, err := cl.Get("https://www.googleapis.com/compute/v1/projects/coreos-cloud/global/images")
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+
+ type coreOSImage struct {
+ SelfLink string
+ CreationTimestamp time.Time
+ Name string
+ }
+
+ type coreOSImageList struct {
+ Items []coreOSImage
+ }
+
+ imageList := &coreOSImageList{}
+ if err := json.NewDecoder(resp.Body).Decode(imageList); err != nil {
+ return "", err
+ }
+ if imageList == nil || len(imageList.Items) == 0 {
+ return "", errors.New("no images list in response")
+ }
+
+ imageURL := ""
+ var max time.Time // latest stable image creation time
+ for _, v := range imageList.Items {
+ if !strings.HasPrefix(v.Name, "coreos-stable") {
+ continue
+ }
+ if v.CreationTimestamp.After(max) {
+ max = v.CreationTimestamp
+ imageURL = v.SelfLink
+ }
+ }
+ if imageURL == "" {
+ return "", errors.New("no stable coreOS image found")
+ }
+ return imageURL, nil
+}
+
+// InstanceGroupAndManager contains both an InstanceGroup and
+// its InstanceGroupManager, if any.
+type InstanceGroupAndManager struct {
+ Group *compute.InstanceGroup
+
+ // Manager is the manager of the Group. It may be nil.
+ Manager *compute.InstanceGroupManager
+}
+
+// InstanceGroups returns all the instance groups in a project's zone, along
+// with their associated InstanceGroupManagers.
+// The returned map is keyed by the instance group identifier URL.
+func InstanceGroups(svc *compute.Service, proj, zone string) (map[string]InstanceGroupAndManager, error) {
+ managerList, err := svc.InstanceGroupManagers.List(proj, zone).Do()
+ if err != nil {
+ return nil, err
+ }
+ if managerList.NextPageToken != "" {
+ return nil, errors.New("too many managers; pagination not supported")
+ }
+ managedBy := make(map[string]*compute.InstanceGroupManager) // instance group URL -> its manager
+ for _, it := range managerList.Items {
+ managedBy[it.InstanceGroup] = it
+ }
+ groupList, err := svc.InstanceGroups.List(proj, zone).Do()
+ if err != nil {
+ return nil, err
+ }
+ if groupList.NextPageToken != "" {
+ return nil, errors.New("too many instance groups; pagination not supported")
+ }
+ ret := make(map[string]InstanceGroupAndManager)
+ for _, it := range groupList.Items {
+ ret[it.SelfLink] = InstanceGroupAndManager{it, managedBy[it.SelfLink]}
+ }
+ return ret, nil
+}
diff --git a/vendor/go4.org/cloud/google/gcsutil/storage.go b/vendor/go4.org/cloud/google/gcsutil/storage.go
new file mode 100644
index 0000000..c6e153a
--- /dev/null
+++ b/vendor/go4.org/cloud/google/gcsutil/storage.go
@@ -0,0 +1,180 @@
+/*
+Copyright 2015 The Go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package gcsutil provides tools for accessing Google Cloud Storage until they can be
+// completely replaced by cloud.google.com/go/storage.
+package gcsutil // import "go4.org/cloud/google/gcsutil"
+
+import (
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
+
+ "cloud.google.com/go/storage"
+ "go4.org/ctxutil"
+ "golang.org/x/net/context"
+)
+
+const gsAccessURL = "https://storage.googleapis.com"
+
+// An Object holds the name of an object (its bucket and key) within
+// Google Cloud Storage.
+type Object struct {
+ Bucket string
+ Key string
+}
+
+func (o *Object) valid() error {
+ if o == nil {
+ return errors.New("invalid nil Object")
+ }
+ if o.Bucket == "" {
+ return errors.New("missing required Bucket field in Object")
+ }
+ if o.Key == "" {
+ return errors.New("missing required Key field in Object")
+ }
+ return nil
+}
+
+// A SizedObject holds the bucket, key, and size of an object.
+type SizedObject struct {
+ Object
+ Size int64
+}
+
+func (o *Object) String() string {
+ if o == nil {
+ return ""
+ }
+ return fmt.Sprintf("%v/%v", o.Bucket, o.Key)
+}
+
+func (so SizedObject) String() string {
+ return fmt.Sprintf("%v/%v (%vB)", so.Bucket, so.Key, so.Size)
+}
+
+// Makes a simple body-less google storage request
+func simpleRequest(method, url_ string) (*http.Request, error) {
+ req, err := http.NewRequest(method, url_, nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("x-goog-api-version", "2")
+ return req, err
+}
+
+// ErrInvalidRange is used when the server has returned http.StatusRequestedRangeNotSatisfiable.
+var ErrInvalidRange = errors.New("gcsutil: requested range not satisfiable")
+
+// GetPartialObject fetches part of a Google Cloud Storage object.
+// This function relies on the ctx ctxutil.HTTPClient value being set to an OAuth2
+// authorized and authenticated HTTP client.
+// If length is negative, the rest of the object is returned.
+// It returns ErrInvalidRange if the server replies with http.StatusRequestedRangeNotSatisfiable.
+// The caller must call Close on the returned value.
+func GetPartialObject(ctx context.Context, obj Object, offset, length int64) (io.ReadCloser, error) {
+ if offset < 0 {
+ return nil, errors.New("invalid negative offset")
+ }
+ if err := obj.valid(); err != nil {
+ return nil, err
+ }
+
+ req, err := simpleRequest("GET", gsAccessURL+"/"+obj.Bucket+"/"+obj.Key)
+ if err != nil {
+ return nil, err
+ }
+ if length >= 0 {
+ req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length-1))
+ } else {
+ req.Header.Set("Range", fmt.Sprintf("bytes=%d-", offset))
+ }
+ req.Cancel = ctx.Done()
+ res, err := ctxutil.Client(ctx).Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("GET (offset=%d, length=%d) failed: %v\n", offset, length, err)
+ }
+ if res.StatusCode == http.StatusNotFound {
+ res.Body.Close()
+ return nil, os.ErrNotExist
+ }
+ if !(res.StatusCode == http.StatusPartialContent || (offset == 0 && res.StatusCode == http.StatusOK)) {
+ res.Body.Close()
+ if res.StatusCode == http.StatusRequestedRangeNotSatisfiable {
+ return nil, ErrInvalidRange
+ }
+ return nil, fmt.Errorf("GET (offset=%d, length=%d) got failed status: %v\n", offset, length, res.Status)
+ }
+
+ return res.Body, nil
+}
+
+// EnumerateObjects lists the objects in a bucket.
+// This function relies on the ctx oauth2.HTTPClient value being set to an OAuth2
+// authorized and authenticated HTTP client.
+// If after is non-empty, listing will begin with lexically greater object names.
+// If limit is non-zero, the length of the list will be limited to that number.
+func EnumerateObjects(ctx context.Context, bucket, after string, limit int) ([]*storage.ObjectAttrs, error) {
+ // Build url, with query params
+ var params []string
+ if after != "" {
+ params = append(params, "marker="+url.QueryEscape(after))
+ }
+ if limit > 0 {
+ params = append(params, fmt.Sprintf("max-keys=%v", limit))
+ }
+ query := ""
+ if len(params) > 0 {
+ query = "?" + strings.Join(params, "&")
+ }
+
+ req, err := simpleRequest("GET", gsAccessURL+"/"+bucket+"/"+query)
+ if err != nil {
+ return nil, err
+ }
+ req.Cancel = ctx.Done()
+ res, err := ctxutil.Client(ctx).Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+ if res.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("gcsutil: bad enumerate response code: %v", res.Status)
+ }
+
+ var xres struct {
+ Contents []SizedObject
+ }
+ if err = xml.NewDecoder(res.Body).Decode(&xres); err != nil {
+ return nil, err
+ }
+
+ objAttrs := make([]*storage.ObjectAttrs, len(xres.Contents))
+ for k, o := range xres.Contents {
+ objAttrs[k] = &storage.ObjectAttrs{
+ Name: o.Key,
+ Size: o.Size,
+ }
+ }
+
+ return objAttrs, nil
+}
diff --git a/vendor/go4.org/ctxutil/ctxutil.go b/vendor/go4.org/ctxutil/ctxutil.go
new file mode 100644
index 0000000..f5842a6
--- /dev/null
+++ b/vendor/go4.org/ctxutil/ctxutil.go
@@ -0,0 +1,44 @@
+/*
+Copyright 2015 The Go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package ctxutil contains golang.org/x/net/context related utilities.
+package ctxutil // import "go4.org/ctxutil"
+
+import (
+ "net/http"
+
+ "golang.org/x/net/context"
+ "golang.org/x/oauth2"
+)
+
+// HTTPClient is the context key to use with golang.org/x/net/context's WithValue function
+// to associate an *http.Client value with a context.
+//
+// We use the same value as the oauth2 package (which first introduced this key) rather
+// than creating a new one and forcing users to possibly set two.
+var HTTPClient = oauth2.HTTPClient
+
+// Client returns the HTTP client to use for the provided context.
+// If ctx is non-nil and has an associated HTTP client, that client is returned.
+// Otherwise, http.DefaultClient is returned.
+func Client(ctx context.Context) *http.Client {
+ if ctx != nil {
+ if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok {
+ return hc
+ }
+ }
+ return http.DefaultClient
+}
diff --git a/vendor/go4.org/errorutil/highlight.go b/vendor/go4.org/errorutil/highlight.go
new file mode 100644
index 0000000..1b1efb0
--- /dev/null
+++ b/vendor/go4.org/errorutil/highlight.go
@@ -0,0 +1,58 @@
+/*
+Copyright 2011 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package errorutil helps make better error messages.
+package errorutil // import "go4.org/errorutil"
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "strings"
+)
+
+// HighlightBytePosition takes a reader and the location in bytes of a parse
+// error (for instance, from json.SyntaxError.Offset) and returns the line, column,
+// and pretty-printed context around the error with an arrow indicating the exact
+// position of the syntax error.
+func HighlightBytePosition(f io.Reader, pos int64) (line, col int, highlight string) {
+ line = 1
+ br := bufio.NewReader(f)
+ lastLine := ""
+ thisLine := new(bytes.Buffer)
+ for n := int64(0); n < pos; n++ {
+ b, err := br.ReadByte()
+ if err != nil {
+ break
+ }
+ if b == '\n' {
+ lastLine = thisLine.String()
+ thisLine.Reset()
+ line++
+ col = 1
+ } else {
+ col++
+ thisLine.WriteByte(b)
+ }
+ }
+ if line > 1 {
+ highlight += fmt.Sprintf("%5d: %s\n", line-1, lastLine)
+ }
+ highlight += fmt.Sprintf("%5d: %s\n", line, thisLine.String())
+ highlight += fmt.Sprintf("%s^\n", strings.Repeat(" ", col+5))
+ return
+}
diff --git a/vendor/go4.org/fault/fault.go b/vendor/go4.org/fault/fault.go
new file mode 100644
index 0000000..25cbdc7
--- /dev/null
+++ b/vendor/go4.org/fault/fault.go
@@ -0,0 +1,59 @@
+/*
+Copyright 2014 The Go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package fault handles fault injection for testing.
+package fault // import "go4.org/fault"
+
+import (
+ "errors"
+ "math/rand"
+ "os"
+ "strconv"
+ "strings"
+)
+
+var fakeErr = errors.New("fake injected error for testing")
+
+// An Injector reports whether fake errors should be returned.
+type Injector struct {
+ failPercent int
+}
+
+// NewInjector returns a new fault injector with the given name. The
+// environment variable "FAULT_" + capital(name) + "_FAIL_PERCENT"
+// controls the percentage of requests that fail. If undefined or
+// zero, no requests fail.
+func NewInjector(name string) *Injector {
+ var failPercent, _ = strconv.Atoi(os.Getenv("FAULT_" + strings.ToUpper(name) + "_FAIL_PERCENT"))
+ return &Injector{
+ failPercent: failPercent,
+ }
+}
+
+// ShouldFail reports whether a fake error should be returned.
+func (in *Injector) ShouldFail() bool {
+ return in.failPercent > 0 && in.failPercent > rand.Intn(100)
+}
+
+// FailErr checks ShouldFail and, if true, assigns a fake error to err
+// and returns true.
+func (in *Injector) FailErr(err *error) bool {
+ if !in.ShouldFail() {
+ return false
+ }
+ *err = fakeErr
+ return true
+}
diff --git a/vendor/go4.org/go4test/cloudlaunch/serve_on_cloud.go b/vendor/go4.org/go4test/cloudlaunch/serve_on_cloud.go
new file mode 100644
index 0000000..78d1420
--- /dev/null
+++ b/vendor/go4.org/go4test/cloudlaunch/serve_on_cloud.go
@@ -0,0 +1,95 @@
+// +build ignore
+
+/*
+Copyright 2016 The Go4 Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// The serve_on_cloud program deploys an HTTP server on Google Compute Engine,
+// serving from Google Cloud Storage. Its purpose is to help testing
+// go4.org/cloud/cloudlaunch and go4.org/wkfs/gcs.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "net/http"
+ "os"
+ "path"
+ "regexp"
+ "time"
+
+ "go4.org/cloud/cloudlaunch"
+ "go4.org/wkfs"
+ _ "go4.org/wkfs/gcs"
+
+ "cloud.google.com/go/compute/metadata"
+ storageapi "google.golang.org/api/storage/v1"
+ compute "google.golang.org/api/compute/v1"
+)
+
+var httpAddr = flag.String("http", ":80", "HTTP address")
+
+var gcsBucket string
+
+func serveHTTP(w http.ResponseWriter, r *http.Request) {
+ rc, err := wkfs.Open(path.Join("/gcs", gcsBucket, r.URL.Path))
+ if err != nil {
+ http.Error(w, fmt.Sprintf("could not open %v: %v", r.URL.Path, err), 500)
+ return
+ }
+ defer rc.Close()
+ http.ServeContent(w, r, r.URL.Path, time.Now(), rc)
+}
+
+func main() {
+ if !metadata.OnGCE() {
+ bucket := os.Getenv("GCSBUCKET")
+ if bucket == "" {
+ log.Fatal("You need to set the GCSBUCKET env var to specify the Google Cloud Storage bucket to serve from.")
+ }
+ projectID := os.Getenv("GCEPROJECTID")
+ if projectID == "" {
+ log.Fatal("You need to set the GCEPROJECTID env var to specify the Google Cloud project where the instance will run.")
+ }
+ (&cloudlaunch.Config{
+ Name: "serveoncloud",
+ BinaryBucket: bucket,
+ GCEProjectID: projectID,
+ Scopes: []string{
+ storageapi.DevstorageFullControlScope,
+ compute.ComputeScope,
+ },
+ }).MaybeDeploy()
+ return
+ }
+
+ flag.Parse()
+
+ storageURLRxp := regexp.MustCompile(`https://storage.googleapis.com/(.+?)/serveoncloud.*`)
+ cloudConfig, err := metadata.InstanceAttributeValue("user-data")
+ if err != nil || cloudConfig == "" {
+ log.Fatalf("could not get cloud config from metadata: %v", err)
+ }
+ m := storageURLRxp.FindStringSubmatch(cloudConfig)
+ if len(m) < 2 {
+ log.Fatal("storage URL not found in cloud config")
+ }
+ gcsBucket = m[1]
+
+ http.HandleFunc("/", serveHTTP)
+
+ log.Fatal(http.ListenAndServe(*httpAddr, nil))
+}
diff --git a/vendor/go4.org/jsonconfig/eval.go b/vendor/go4.org/jsonconfig/eval.go
new file mode 100644
index 0000000..988597d
--- /dev/null
+++ b/vendor/go4.org/jsonconfig/eval.go
@@ -0,0 +1,321 @@
+/*
+Copyright 2011 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package jsonconfig
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strconv"
+ "strings"
+
+ "go4.org/errorutil"
+ "go4.org/wkfs"
+)
+
+type stringVector struct {
+ v []string
+}
+
+func (v *stringVector) Push(s string) {
+ v.v = append(v.v, s)
+}
+
+func (v *stringVector) Pop() {
+ v.v = v.v[:len(v.v)-1]
+}
+
+func (v *stringVector) Last() string {
+ return v.v[len(v.v)-1]
+}
+
+// A File is the type returned by ConfigParser.Open.
+type File interface {
+ io.ReadSeeker
+ io.Closer
+ Name() string
+}
+
+// ConfigParser specifies the environment for parsing a config file
+// and evaluating expressions.
+type ConfigParser struct {
+ rootJSON Obj
+
+ touchedFiles map[string]bool
+ includeStack stringVector
+
+ // Open optionally specifies an opener function.
+ Open func(filename string) (File, error)
+
+ // IncludeDirs optionally specifies where to find the other config files which are child
+ // objects of this config, if any. Even if nil, the working directory is always searched
+ // first.
+ IncludeDirs []string
+}
+
+func (c *ConfigParser) open(filename string) (File, error) {
+ if c.Open == nil {
+ return wkfs.Open(filename)
+ }
+ return c.Open(filename)
+}
+
+// Validates variable names for config _env expresssions
+var envPattern = regexp.MustCompile(`\$\{[A-Za-z0-9_]+\}`)
+
+// ReadFile parses the provided path and returns the config file.
+// If path is empty, the c.Open function must be defined.
+func (c *ConfigParser) ReadFile(path string) (Obj, error) {
+ if path == "" && c.Open == nil {
+ return nil, errors.New("ReadFile of empty string but Open hook not defined")
+ }
+ c.touchedFiles = make(map[string]bool)
+ var err error
+ c.rootJSON, err = c.recursiveReadJSON(path)
+ return c.rootJSON, err
+}
+
+// Decodes and evaluates a json config file, watching for include cycles.
+func (c *ConfigParser) recursiveReadJSON(configPath string) (decodedObject map[string]interface{}, err error) {
+ if configPath != "" {
+ absConfigPath, err := filepath.Abs(configPath)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to expand absolute path for %s", configPath)
+ }
+ if c.touchedFiles[absConfigPath] {
+ return nil, fmt.Errorf("ConfigParser include cycle detected reading config: %v",
+ absConfigPath)
+ }
+ c.touchedFiles[absConfigPath] = true
+
+ c.includeStack.Push(absConfigPath)
+ defer c.includeStack.Pop()
+ }
+
+ var f File
+ if f, err = c.open(configPath); err != nil {
+ return nil, fmt.Errorf("Failed to open config: %v", err)
+ }
+ defer f.Close()
+
+ decodedObject = make(map[string]interface{})
+ dj := json.NewDecoder(f)
+ if err = dj.Decode(&decodedObject); err != nil {
+ extra := ""
+ if serr, ok := err.(*json.SyntaxError); ok {
+ if _, serr := f.Seek(0, os.SEEK_SET); serr != nil {
+ log.Fatalf("seek error: %v", serr)
+ }
+ line, col, highlight := errorutil.HighlightBytePosition(f, serr.Offset)
+ extra = fmt.Sprintf(":\nError at line %d, column %d (file offset %d):\n%s",
+ line, col, serr.Offset, highlight)
+ }
+ return nil, fmt.Errorf("error parsing JSON object in config file %s%s\n%v",
+ f.Name(), extra, err)
+ }
+
+ if err = c.evaluateExpressions(decodedObject, nil, false); err != nil {
+ return nil, fmt.Errorf("error expanding JSON config expressions in %s:\n%v",
+ f.Name(), err)
+ }
+
+ return decodedObject, nil
+}
+
+var regFunc = map[string]expanderFunc{}
+
+// RegisterFunc registers a new function that may be called from JSON
+// configs using an array of the form ["_name", arg0, argN...].
+// The provided name must begin with an underscore.
+func RegisterFunc(name string, fn func(c *ConfigParser, v []interface{}) (interface{}, error)) {
+ if len(name) < 2 || !strings.HasPrefix(name, "_") {
+ panic("illegal name")
+ }
+ if _, dup := regFunc[name]; dup {
+ panic("duplicate registration of " + name)
+ }
+ regFunc[name] = fn
+}
+
+type expanderFunc func(c *ConfigParser, v []interface{}) (interface{}, error)
+
+func namedExpander(name string) (fn expanderFunc, ok bool) {
+ switch name {
+ case "_env":
+ return (*ConfigParser).expandEnv, true
+ case "_fileobj":
+ return (*ConfigParser).expandFile, true
+ }
+ fn, ok = regFunc[name]
+ return
+}
+
+func (c *ConfigParser) evalValue(v interface{}) (interface{}, error) {
+ sl, ok := v.([]interface{})
+ if !ok {
+ return v, nil
+ }
+ if name, ok := sl[0].(string); ok {
+ if expander, ok := namedExpander(name); ok {
+ newval, err := expander(c, sl[1:])
+ if err != nil {
+ return nil, err
+ }
+ return newval, nil
+ }
+ }
+ for i, oldval := range sl {
+ newval, err := c.evalValue(oldval)
+ if err != nil {
+ return nil, err
+ }
+ sl[i] = newval
+ }
+ return v, nil
+}
+
+// CheckTypes parses m and returns an error if it encounters a type or value
+// that is not supported by this package.
+func (c *ConfigParser) CheckTypes(m map[string]interface{}) error {
+ return c.evaluateExpressions(m, nil, true)
+}
+
+// evaluateExpressions parses recursively m, populating it with the values
+// that are found, unless testOnly is true.
+func (c *ConfigParser) evaluateExpressions(m map[string]interface{}, seenKeys []string, testOnly bool) error {
+ for k, ei := range m {
+ thisPath := append(seenKeys, k)
+ switch subval := ei.(type) {
+ case string, bool, float64, nil:
+ continue
+ case []interface{}:
+ if len(subval) == 0 {
+ continue
+ }
+ evaled, err := c.evalValue(subval)
+ if err != nil {
+ return fmt.Errorf("%s: value error %v", strings.Join(thisPath, "."), err)
+ }
+ if !testOnly {
+ m[k] = evaled
+ }
+ case map[string]interface{}:
+ if err := c.evaluateExpressions(subval, thisPath, testOnly); err != nil {
+ return err
+ }
+ default:
+ return fmt.Errorf("%s: unhandled type %T", strings.Join(thisPath, "."), ei)
+ }
+ }
+ return nil
+}
+
+// Permit either:
+// ["_env", "VARIABLE"] (required to be set)
+// or ["_env", "VARIABLE", "default_value"]
+func (c *ConfigParser) expandEnv(v []interface{}) (interface{}, error) {
+ hasDefault := false
+ def := ""
+ if len(v) < 1 || len(v) > 2 {
+ return "", fmt.Errorf("_env expansion expected 1 or 2 args, got %d", len(v))
+ }
+ s, ok := v[0].(string)
+ if !ok {
+ return "", fmt.Errorf("Expected a string after _env expansion; got %#v", v[0])
+ }
+ boolDefault, wantsBool := false, false
+ if len(v) == 2 {
+ hasDefault = true
+ switch vdef := v[1].(type) {
+ case string:
+ def = vdef
+ case bool:
+ wantsBool = true
+ boolDefault = vdef
+ default:
+ return "", fmt.Errorf("Expected default value in %q _env expansion; got %#v", s, v[1])
+ }
+ }
+ var err error
+ expanded := envPattern.ReplaceAllStringFunc(s, func(match string) string {
+ envVar := match[2 : len(match)-1]
+ val := os.Getenv(envVar)
+ // Special case:
+ if val == "" && envVar == "USER" && runtime.GOOS == "windows" {
+ val = os.Getenv("USERNAME")
+ }
+ if val == "" {
+ if hasDefault {
+ return def
+ }
+ err = fmt.Errorf("couldn't expand environment variable %q", envVar)
+ }
+ return val
+ })
+ if wantsBool {
+ if expanded == "" {
+ return boolDefault, nil
+ }
+ return strconv.ParseBool(expanded)
+ }
+ return expanded, err
+}
+
+func (c *ConfigParser) expandFile(v []interface{}) (exp interface{}, err error) {
+ if len(v) != 1 {
+ return "", fmt.Errorf("_file expansion expected 1 arg, got %d", len(v))
+ }
+ var incPath string
+ if incPath, err = c.ConfigFilePath(v[0].(string)); err != nil {
+ return "", fmt.Errorf("Included config does not exist: %v", v[0])
+ }
+ if exp, err = c.recursiveReadJSON(incPath); err != nil {
+ return "", fmt.Errorf("In file included from %s:\n%v",
+ c.includeStack.Last(), err)
+ }
+ return exp, nil
+}
+
+// ConfigFilePath checks if configFile is found and returns a usable path to it.
+// It first checks if configFile is an absolute path, or if it's found in the
+// current working directory. If not, it then checks if configFile is in one of
+// c.IncludeDirs. It returns an error if configFile is absolute and could not be
+// statted, or os.ErrNotExist if configFile was not found.
+func (c *ConfigParser) ConfigFilePath(configFile string) (path string, err error) {
+ // Try to open as absolute / relative to CWD
+ _, err = os.Stat(configFile)
+ if err != nil && filepath.IsAbs(configFile) {
+ return "", err
+ }
+ if err == nil {
+ return configFile, nil
+ }
+
+ for _, d := range c.IncludeDirs {
+ if _, err := os.Stat(filepath.Join(d, configFile)); err == nil {
+ return filepath.Join(d, configFile), nil
+ }
+ }
+
+ return "", os.ErrNotExist
+}
diff --git a/vendor/go4.org/jsonconfig/jsonconfig.go b/vendor/go4.org/jsonconfig/jsonconfig.go
new file mode 100644
index 0000000..386dda8
--- /dev/null
+++ b/vendor/go4.org/jsonconfig/jsonconfig.go
@@ -0,0 +1,297 @@
+/*
+Copyright 2011 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package jsonconfig defines a helper type for JSON objects to be
+// used for configuration.
+package jsonconfig // import "go4.org/jsonconfig"
+
+import (
+ "fmt"
+ "sort"
+ "strconv"
+ "strings"
+)
+
+// Obj is a JSON configuration map.
+type Obj map[string]interface{}
+
+// ReadFile reads JSON config data from the specified open file, expanding
+// all expressions. Use *ConfigParser.ReadFile instead if you
+// need to set c.IncludeDirs.
+func ReadFile(configPath string) (Obj, error) {
+ var c ConfigParser
+ return c.ReadFile(configPath)
+}
+
+func (jc Obj) RequiredObject(key string) Obj {
+ return jc.obj(key, false)
+}
+
+func (jc Obj) OptionalObject(key string) Obj {
+ return jc.obj(key, true)
+}
+
+func (jc Obj) obj(key string, optional bool) Obj {
+ jc.noteKnownKey(key)
+ ei, ok := jc[key]
+ if !ok {
+ if optional {
+ return make(Obj)
+ }
+ jc.appendError(fmt.Errorf("Missing required config key %q (object)", key))
+ return make(Obj)
+ }
+ m, ok := ei.(map[string]interface{})
+ if !ok {
+ jc.appendError(fmt.Errorf("Expected config key %q to be an object, not %T", key, ei))
+ return make(Obj)
+ }
+ return m
+}
+
+func (jc Obj) RequiredString(key string) string {
+ return jc.string(key, nil)
+}
+
+func (jc Obj) OptionalString(key, def string) string {
+ return jc.string(key, &def)
+}
+
+func (jc Obj) string(key string, def *string) string {
+ jc.noteKnownKey(key)
+ ei, ok := jc[key]
+ if !ok {
+ if def != nil {
+ return *def
+ }
+ jc.appendError(fmt.Errorf("Missing required config key %q (string)", key))
+ return ""
+ }
+ s, ok := ei.(string)
+ if !ok {
+ jc.appendError(fmt.Errorf("Expected config key %q to be a string", key))
+ return ""
+ }
+ return s
+}
+
+func (jc Obj) RequiredStringOrObject(key string) interface{} {
+ return jc.stringOrObject(key, true)
+}
+
+func (jc Obj) OptionalStringOrObject(key string) interface{} {
+ return jc.stringOrObject(key, false)
+}
+
+func (jc Obj) stringOrObject(key string, required bool) interface{} {
+ jc.noteKnownKey(key)
+ ei, ok := jc[key]
+ if !ok {
+ if !required {
+ return nil
+ }
+ jc.appendError(fmt.Errorf("Missing required config key %q (string or object)", key))
+ return ""
+ }
+ if _, ok := ei.(map[string]interface{}); ok {
+ return ei
+ }
+ if _, ok := ei.(string); ok {
+ return ei
+ }
+ jc.appendError(fmt.Errorf("Expected config key %q to be a string or object", key))
+ return ""
+}
+
+func (jc Obj) RequiredBool(key string) bool {
+ return jc.bool(key, nil)
+}
+
+func (jc Obj) OptionalBool(key string, def bool) bool {
+ return jc.bool(key, &def)
+}
+
+func (jc Obj) bool(key string, def *bool) bool {
+ jc.noteKnownKey(key)
+ ei, ok := jc[key]
+ if !ok {
+ if def != nil {
+ return *def
+ }
+ jc.appendError(fmt.Errorf("Missing required config key %q (boolean)", key))
+ return false
+ }
+ switch v := ei.(type) {
+ case bool:
+ return v
+ case string:
+ b, err := strconv.ParseBool(v)
+ if err != nil {
+ jc.appendError(fmt.Errorf("Config key %q has bad boolean format %q", key, v))
+ }
+ return b
+ default:
+ jc.appendError(fmt.Errorf("Expected config key %q to be a boolean", key))
+ return false
+ }
+}
+
+func (jc Obj) RequiredInt(key string) int {
+ return jc.int(key, nil)
+}
+
+func (jc Obj) OptionalInt(key string, def int) int {
+ return jc.int(key, &def)
+}
+
+func (jc Obj) int(key string, def *int) int {
+ jc.noteKnownKey(key)
+ ei, ok := jc[key]
+ if !ok {
+ if def != nil {
+ return *def
+ }
+ jc.appendError(fmt.Errorf("Missing required config key %q (integer)", key))
+ return 0
+ }
+ b, ok := ei.(float64)
+ if !ok {
+ jc.appendError(fmt.Errorf("Expected config key %q to be a number", key))
+ return 0
+ }
+ return int(b)
+}
+
+func (jc Obj) RequiredInt64(key string) int64 {
+ return jc.int64(key, nil)
+}
+
+func (jc Obj) OptionalInt64(key string, def int64) int64 {
+ return jc.int64(key, &def)
+}
+
+func (jc Obj) int64(key string, def *int64) int64 {
+ jc.noteKnownKey(key)
+ ei, ok := jc[key]
+ if !ok {
+ if def != nil {
+ return *def
+ }
+ jc.appendError(fmt.Errorf("Missing required config key %q (integer)", key))
+ return 0
+ }
+ b, ok := ei.(float64)
+ if !ok {
+ jc.appendError(fmt.Errorf("Expected config key %q to be a number", key))
+ return 0
+ }
+ return int64(b)
+}
+
+func (jc Obj) RequiredList(key string) []string {
+ return jc.requiredList(key, true)
+}
+
+func (jc Obj) OptionalList(key string) []string {
+ return jc.requiredList(key, false)
+}
+
+func (jc Obj) requiredList(key string, required bool) []string {
+ jc.noteKnownKey(key)
+ ei, ok := jc[key]
+ if !ok {
+ if required {
+ jc.appendError(fmt.Errorf("Missing required config key %q (list of strings)", key))
+ }
+ return nil
+ }
+ eil, ok := ei.([]interface{})
+ if !ok {
+ jc.appendError(fmt.Errorf("Expected config key %q to be a list, not %T", key, ei))
+ return nil
+ }
+ sl := make([]string, len(eil))
+ for i, ei := range eil {
+ s, ok := ei.(string)
+ if !ok {
+ jc.appendError(fmt.Errorf("Expected config key %q index %d to be a string, not %T", key, i, ei))
+ return nil
+ }
+ sl[i] = s
+ }
+ return sl
+}
+
+func (jc Obj) noteKnownKey(key string) {
+ _, ok := jc["_knownkeys"]
+ if !ok {
+ jc["_knownkeys"] = make(map[string]bool)
+ }
+ jc["_knownkeys"].(map[string]bool)[key] = true
+}
+
+func (jc Obj) appendError(err error) {
+ ei, ok := jc["_errors"]
+ if ok {
+ jc["_errors"] = append(ei.([]error), err)
+ } else {
+ jc["_errors"] = []error{err}
+ }
+}
+
+// UnknownKeys returns the keys from the config that have not yet been discovered by one of the RequiredT or OptionalT calls.
+func (jc Obj) UnknownKeys() []string {
+ ei, ok := jc["_knownkeys"]
+ var known map[string]bool
+ if ok {
+ known = ei.(map[string]bool)
+ }
+ var unknown []string
+ for k, _ := range jc {
+ if ok && known[k] {
+ continue
+ }
+ if strings.HasPrefix(k, "_") {
+ // Permit keys with a leading underscore as a
+ // form of comments.
+ continue
+ }
+ unknown = append(unknown, k)
+ }
+ sort.Strings(unknown)
+ return unknown
+}
+
+func (jc Obj) Validate() error {
+ unknown := jc.UnknownKeys()
+ for _, k := range unknown {
+ jc.appendError(fmt.Errorf("Unknown key %q", k))
+ }
+
+ ei, ok := jc["_errors"]
+ if !ok {
+ return nil
+ }
+ errList := ei.([]error)
+ if len(errList) == 1 {
+ return errList[0]
+ }
+ strs := make([]string, 0)
+ for _, v := range errList {
+ strs = append(strs, v.Error())
+ }
+ return fmt.Errorf("Multiple errors: " + strings.Join(strs, ", "))
+}
diff --git a/vendor/go4.org/jsonconfig/jsonconfig_test.go b/vendor/go4.org/jsonconfig/jsonconfig_test.go
new file mode 100644
index 0000000..8d2a1bd
--- /dev/null
+++ b/vendor/go4.org/jsonconfig/jsonconfig_test.go
@@ -0,0 +1,114 @@
+/*
+Copyright 2011 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package jsonconfig
+
+import (
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func testIncludes(configFile string, t *testing.T) {
+ var c ConfigParser
+ c.IncludeDirs = []string{"testdata"}
+ obj, err := c.ReadFile(configFile)
+ if err != nil {
+ t.Fatal(err)
+ }
+ two := obj.RequiredObject("two")
+ if err := obj.Validate(); err != nil {
+ t.Error(err)
+ }
+ if g, e := two.RequiredString("key"), "value"; g != e {
+ t.Errorf("sub object key = %q; want %q", g, e)
+ }
+}
+
+func TestIncludesCWD(t *testing.T) {
+ testIncludes("testdata/include1.json", t)
+}
+
+func TestIncludesIncludeDirs(t *testing.T) {
+ testIncludes("testdata/include1bis.json", t)
+}
+
+func TestIncludeLoop(t *testing.T) {
+ _, err := ReadFile("testdata/loop1.json")
+ if err == nil {
+ t.Fatal("expected an error about import cycles.")
+ }
+ if !strings.Contains(err.Error(), "include cycle detected") {
+ t.Fatalf("expected an error about import cycles; got: %v", err)
+ }
+}
+
+func TestBoolEnvs(t *testing.T) {
+ os.Setenv("TEST_EMPTY", "")
+ os.Setenv("TEST_TRUE", "true")
+ os.Setenv("TEST_ONE", "1")
+ os.Setenv("TEST_ZERO", "0")
+ os.Setenv("TEST_FALSE", "false")
+ obj, err := ReadFile("testdata/boolenv.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if str := obj.RequiredString("emptystr"); str != "" {
+ t.Errorf("str = %q, want empty", str)
+ }
+ tests := []struct {
+ key string
+ want bool
+ }{
+ {"def_false", false},
+ {"def_true", true},
+ {"set_true_def_false", true},
+ {"set_false_def_true", false},
+ {"lit_true", true},
+ {"lit_false", false},
+ {"one", true},
+ {"zero", false},
+ }
+ for _, tt := range tests {
+ if v := obj.RequiredBool(tt.key); v != tt.want {
+ t.Errorf("key %q = %v; want %v", tt.key, v, tt.want)
+ }
+ }
+ if err := obj.Validate(); err != nil {
+ t.Error(err)
+ }
+}
+
+func TestListExpansion(t *testing.T) {
+ os.Setenv("TEST_BAR", "bar")
+ obj, err := ReadFile("testdata/listexpand.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ s := obj.RequiredString("str")
+ l := obj.RequiredList("list")
+ if err := obj.Validate(); err != nil {
+ t.Error(err)
+ }
+ want := []string{"foo", "bar"}
+ if !reflect.DeepEqual(l, want) {
+ t.Errorf("got = %#v\nwant = %#v", l, want)
+ }
+ if s != "bar" {
+ t.Errorf("str = %q, want %q", s, "bar")
+ }
+}
diff --git a/vendor/go4.org/jsonconfig/testdata/boolenv.json b/vendor/go4.org/jsonconfig/testdata/boolenv.json
new file mode 100644
index 0000000..fe9431e
--- /dev/null
+++ b/vendor/go4.org/jsonconfig/testdata/boolenv.json
@@ -0,0 +1,11 @@
+{
+ "emptystr": ["_env", "${TEST_EMPTY}", ""],
+ "def_false": ["_env", "${TEST_EMPTY}", false],
+ "def_true": ["_env", "${TEST_EMPTY}", true],
+ "set_true_def_false": ["_env", "${TEST_TRUE}", false],
+ "set_false_def_true": ["_env", "${TEST_FALSE}", true],
+ "one": ["_env", "${TEST_ONE}"],
+ "zero": ["_env", "${TEST_ZERO}"],
+ "lit_true": true,
+ "lit_false": false
+}
diff --git a/vendor/go4.org/jsonconfig/testdata/include1.json b/vendor/go4.org/jsonconfig/testdata/include1.json
new file mode 100644
index 0000000..6d8b38e
--- /dev/null
+++ b/vendor/go4.org/jsonconfig/testdata/include1.json
@@ -0,0 +1,3 @@
+{
+ "two": ["_fileobj", "testdata/include2.json"]
+}
diff --git a/vendor/go4.org/jsonconfig/testdata/include1bis.json b/vendor/go4.org/jsonconfig/testdata/include1bis.json
new file mode 100644
index 0000000..8459f8e
--- /dev/null
+++ b/vendor/go4.org/jsonconfig/testdata/include1bis.json
@@ -0,0 +1,3 @@
+{
+ "two": ["_fileobj", "include2.json"]
+}
diff --git a/vendor/go4.org/jsonconfig/testdata/include2.json b/vendor/go4.org/jsonconfig/testdata/include2.json
new file mode 100644
index 0000000..7a9e864
--- /dev/null
+++ b/vendor/go4.org/jsonconfig/testdata/include2.json
@@ -0,0 +1,3 @@
+{
+ "key": "value"
+}
diff --git a/vendor/go4.org/jsonconfig/testdata/listexpand.json b/vendor/go4.org/jsonconfig/testdata/listexpand.json
new file mode 100644
index 0000000..ccabcef
--- /dev/null
+++ b/vendor/go4.org/jsonconfig/testdata/listexpand.json
@@ -0,0 +1,4 @@
+{
+ "list": ["foo", ["_env", "${TEST_BAR}"]],
+ "str": ["_env", "${TEST_BAR}"]
+}
diff --git a/vendor/go4.org/jsonconfig/testdata/loop1.json b/vendor/go4.org/jsonconfig/testdata/loop1.json
new file mode 100644
index 0000000..215146f
--- /dev/null
+++ b/vendor/go4.org/jsonconfig/testdata/loop1.json
@@ -0,0 +1,3 @@
+{
+ "obj": ["_fileobj", "testdata/loop2.json"]
+}
diff --git a/vendor/go4.org/jsonconfig/testdata/loop2.json b/vendor/go4.org/jsonconfig/testdata/loop2.json
new file mode 100644
index 0000000..1d270eb
--- /dev/null
+++ b/vendor/go4.org/jsonconfig/testdata/loop2.json
@@ -0,0 +1,3 @@
+{
+ "obj": ["_fileobj", "testdata/loop1.json"]
+}
diff --git a/vendor/go4.org/legal/legal.go b/vendor/go4.org/legal/legal.go
new file mode 100644
index 0000000..954b143
--- /dev/null
+++ b/vendor/go4.org/legal/legal.go
@@ -0,0 +1,32 @@
+/*
+Copyright 2014 The Go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package legal provides in-process storage for compiled-in licenses.
+package legal // import "go4.org/legal"
+
+var licenses []string
+
+// RegisterLicense stores the license text.
+// It doesn't check whether the text was already present.
+func RegisterLicense(text string) {
+ licenses = append(licenses, text)
+ return
+}
+
+// Licenses returns a slice of the licenses.
+func Licenses() []string {
+ return licenses
+}
diff --git a/vendor/go4.org/legal/legal_test.go b/vendor/go4.org/legal/legal_test.go
new file mode 100644
index 0000000..0c65e9a
--- /dev/null
+++ b/vendor/go4.org/legal/legal_test.go
@@ -0,0 +1,29 @@
+/*
+Copyright 2014 The Go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package legal
+
+import (
+ "testing"
+)
+
+func TestRegisterLicense(t *testing.T) {
+ initial := len(licenses)
+ RegisterLicense("dummy")
+ if initial+1 != len(licenses) {
+ t.Fatal("didn't add a license")
+ }
+}
diff --git a/vendor/go4.org/lock/.gitignore b/vendor/go4.org/lock/.gitignore
new file mode 100644
index 0000000..b25c15b
--- /dev/null
+++ b/vendor/go4.org/lock/.gitignore
@@ -0,0 +1 @@
+*~
diff --git a/vendor/go4.org/lock/lock.go b/vendor/go4.org/lock/lock.go
new file mode 100644
index 0000000..3e25362
--- /dev/null
+++ b/vendor/go4.org/lock/lock.go
@@ -0,0 +1,186 @@
+/*
+Copyright 2013 The Go Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package lock is a file locking library.
+package lock // import "go4.org/lock"
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "sync"
+)
+
+// Lock locks the given file, creating the file if necessary. If the
+// file already exists, it must have zero size or an error is returned.
+// The lock is an exclusive lock (a write lock), but locked files
+// should neither be read from nor written to. Such files should have
+// zero size and only exist to co-ordinate ownership across processes.
+//
+// A nil Closer is returned if an error occurred. Otherwise, close that
+// Closer to release the lock.
+//
+// On Linux, FreeBSD and OSX, a lock has the same semantics as fcntl(2)'s
+// advisory locks. In particular, closing any other file descriptor for the
+// same file will release the lock prematurely.
+//
+// Attempting to lock a file that is already locked by the current process
+// has undefined behavior.
+//
+// On other operating systems, lock will fallback to using the presence and
+// content of a file named name + '.lock' to implement locking behavior.
+func Lock(name string) (io.Closer, error) {
+ abs, err := filepath.Abs(name)
+ if err != nil {
+ return nil, err
+ }
+ lockmu.Lock()
+ defer lockmu.Unlock()
+ if locked[abs] {
+ return nil, fmt.Errorf("file %q already locked", abs)
+ }
+
+ c, err := lockFn(abs)
+ if err != nil {
+ return nil, fmt.Errorf("cannot acquire lock: %v", err)
+ }
+ locked[abs] = true
+ return c, nil
+}
+
+var lockFn = lockPortable
+
+// lockPortable is a portable version not using fcntl. Doesn't handle crashes as gracefully,
+// since it can leave stale lock files.
+func lockPortable(name string) (io.Closer, error) {
+ fi, err := os.Stat(name)
+ if err == nil && fi.Size() > 0 {
+ st := portableLockStatus(name)
+ switch st {
+ case statusLocked:
+ return nil, fmt.Errorf("file %q already locked", name)
+ case statusStale:
+ os.Remove(name)
+ case statusInvalid:
+ return nil, fmt.Errorf("can't Lock file %q: has invalid contents", name)
+ }
+ }
+ f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_EXCL, 0666)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create lock file %s %v", name, err)
+ }
+ if err := json.NewEncoder(f).Encode(&pidLockMeta{OwnerPID: os.Getpid()}); err != nil {
+ return nil, fmt.Errorf("cannot write owner pid: %v", err)
+ }
+ return &unlocker{
+ f: f,
+ abs: name,
+ portable: true,
+ }, nil
+}
+
+type lockStatus int
+
+const (
+ statusInvalid lockStatus = iota
+ statusLocked
+ statusUnlocked
+ statusStale
+)
+
+type pidLockMeta struct {
+ OwnerPID int
+}
+
+func portableLockStatus(path string) lockStatus {
+ f, err := os.Open(path)
+ if err != nil {
+ return statusUnlocked
+ }
+ defer f.Close()
+ var meta pidLockMeta
+ if json.NewDecoder(f).Decode(&meta) != nil {
+ return statusInvalid
+ }
+ if meta.OwnerPID == 0 {
+ return statusInvalid
+ }
+ p, err := os.FindProcess(meta.OwnerPID)
+ if err != nil {
+ // e.g. on Windows
+ return statusStale
+ }
+ // On unix, os.FindProcess always is true, so we have to send
+ // it a signal to see if it's alive.
+ if signalZero != nil {
+ if p.Signal(signalZero) != nil {
+ return statusStale
+ }
+ }
+ return statusLocked
+}
+
+var signalZero os.Signal // nil or set by lock_sigzero.go
+
+var (
+ lockmu sync.Mutex
+ locked = map[string]bool{} // abs path -> true
+)
+
+type unlocker struct {
+ portable bool
+ f *os.File
+ abs string
+ // once guards the close method call.
+ once sync.Once
+ // err holds the error returned by Close.
+ err error
+}
+
+func (u *unlocker) Close() error {
+ u.once.Do(u.close)
+ return u.err
+}
+
+func (u *unlocker) close() {
+ lockmu.Lock()
+ defer lockmu.Unlock()
+ delete(locked, u.abs)
+
+ if u.portable {
+ // In the portable lock implementation, it's
+ // important to close before removing because
+ // Windows won't allow us to remove an open
+ // file.
+ if err := u.f.Close(); err != nil {
+ u.err = err
+ }
+ if err := os.Remove(u.abs); err != nil {
+ // Note that if both Close and Remove fail,
+ // we care more about the latter than the former
+ // so we'll return that error.
+ u.err = err
+ }
+ return
+ }
+ // In other implementatioons, it's nice for us to clean up.
+ // If we do do this, though, it needs to be before the
+ // u.f.Close below.
+ os.Remove(u.abs)
+ u.err = u.f.Close()
+}
diff --git a/vendor/go4.org/lock/lock_appengine.go b/vendor/go4.org/lock/lock_appengine.go
new file mode 100644
index 0000000..ab4cad6
--- /dev/null
+++ b/vendor/go4.org/lock/lock_appengine.go
@@ -0,0 +1,32 @@
+// +build appengine
+
+/*
+Copyright 2013 The Go Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package lock
+
+import (
+ "errors"
+ "io"
+)
+
+func init() {
+ lockFn = lockAppEngine
+}
+
+func lockAppEngine(name string) (io.Closer, error) {
+ return nil, errors.New("Lock not available on App Engine")
+}
diff --git a/vendor/go4.org/lock/lock_plan9.go b/vendor/go4.org/lock/lock_plan9.go
new file mode 100644
index 0000000..d841c27
--- /dev/null
+++ b/vendor/go4.org/lock/lock_plan9.go
@@ -0,0 +1,41 @@
+/*
+Copyright 2013 The Go Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package lock
+
+import (
+ "fmt"
+ "io"
+ "os"
+)
+
+func init() {
+ lockFn = lockPlan9
+}
+
+func lockPlan9(name string) (io.Closer, error) {
+ fi, err := os.Stat(name)
+ if err == nil && fi.Size() > 0 {
+ return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
+ }
+
+ f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, os.ModeExclusive|0644)
+ if err != nil {
+ return nil, fmt.Errorf("Lock Create of %s failed: %v", name, err)
+ }
+
+ return &unlocker{f: f, abs: name}, nil
+}
diff --git a/vendor/go4.org/lock/lock_sigzero.go b/vendor/go4.org/lock/lock_sigzero.go
new file mode 100644
index 0000000..fd3ba2d
--- /dev/null
+++ b/vendor/go4.org/lock/lock_sigzero.go
@@ -0,0 +1,26 @@
+// +build !appengine
+// +build linux darwin freebsd openbsd netbsd dragonfly
+
+/*
+Copyright 2013 The Go Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package lock
+
+import "syscall"
+
+func init() {
+ signalZero = syscall.Signal(0)
+}
diff --git a/vendor/go4.org/lock/lock_test.go b/vendor/go4.org/lock/lock_test.go
new file mode 100644
index 0000000..de9c8f8
--- /dev/null
+++ b/vendor/go4.org/lock/lock_test.go
@@ -0,0 +1,222 @@
+/*
+Copyright 2013 The Go Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package lock
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "testing"
+)
+
+func TestLock(t *testing.T) {
+ testLock(t, false)
+}
+
+func TestLockPortable(t *testing.T) {
+ testLock(t, true)
+}
+
+func TestLockInChild(t *testing.T) {
+ f := os.Getenv("TEST_LOCK_FILE")
+ if f == "" {
+ // not child
+ return
+ }
+ lock := Lock
+ if v, _ := strconv.ParseBool(os.Getenv("TEST_LOCK_PORTABLE")); v {
+ lock = lockPortable
+ }
+
+ var lk io.Closer
+ for scan := bufio.NewScanner(os.Stdin); scan.Scan(); {
+ var err error
+ switch scan.Text() {
+ case "lock":
+ lk, err = lock(f)
+ case "unlock":
+ err = lk.Close()
+ lk = nil
+ case "exit":
+ // Simulate a crash, or at least not unlocking the lock.
+ os.Exit(0)
+ default:
+ err = fmt.Errorf("unexpected child command %q", scan.Text())
+ }
+ if err != nil {
+ fmt.Println(err)
+ } else {
+ fmt.Println("")
+ }
+ }
+}
+
+func testLock(t *testing.T, portable bool) {
+ lock := Lock
+ if portable {
+ lock = lockPortable
+ }
+ t.Logf("test lock, portable %v", portable)
+
+ td, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(td)
+
+ path := filepath.Join(td, "foo.lock")
+
+ proc := newChildProc(t, path, portable)
+ defer proc.kill()
+
+ t.Logf("First lock in child")
+ if err := proc.do("lock"); err != nil {
+ t.Fatalf("first lock in child process: %v", err)
+ }
+
+ t.Logf("Crash child")
+ if err := proc.do("exit"); err != nil {
+ t.Fatalf("crash in child process: %v", err)
+ }
+
+ proc = newChildProc(t, path, portable)
+ defer proc.kill()
+
+ t.Logf("Locking+unlocking in child...")
+ if err := proc.do("lock"); err != nil {
+ t.Fatalf("lock in child process after crashing child: %v", err)
+ }
+ if err := proc.do("unlock"); err != nil {
+ t.Fatalf("lock in child process after crashing child: %v", err)
+ }
+
+ t.Logf("Locking in parent...")
+ lk1, err := lock(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Logf("Again in parent...")
+ _, err = lock(path)
+ if err == nil {
+ t.Fatal("expected second lock to fail")
+ }
+
+ t.Logf("Locking in child...")
+ if err := proc.do("lock"); err == nil {
+ t.Fatalf("expected lock in child process to fail")
+ }
+
+ t.Logf("Unlocking lock in parent")
+ if err := lk1.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+ t.Logf("Trying lock again in child...")
+ if err := proc.do("lock"); err != nil {
+ t.Fatal(err)
+ }
+ if err := proc.do("unlock"); err != nil {
+ t.Fatal(err)
+ }
+
+ lk3, err := lock(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ lk3.Close()
+}
+
+type childLockCmd struct {
+ op string
+ reply chan<- error
+}
+
+type childProc struct {
+ proc *os.Process
+ c chan childLockCmd
+}
+
+func (c *childProc) kill() {
+ c.proc.Kill()
+}
+
+func (c *childProc) do(op string) error {
+ reply := make(chan error)
+ c.c <- childLockCmd{
+ op: op,
+ reply: reply,
+ }
+ return <-reply
+}
+
+func newChildProc(t *testing.T, path string, portable bool) *childProc {
+ cmd := exec.Command(os.Args[0], "-test.run=LockInChild$")
+ cmd.Env = []string{"TEST_LOCK_FILE=" + path}
+ toChild, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatalf("cannot make pipe: %v", err)
+ }
+ fromChild, err := cmd.StdoutPipe()
+ if err != nil {
+ t.Fatalf("cannot make pipe: %v", err)
+ }
+ cmd.Stderr = os.Stderr
+ if portable {
+ cmd.Env = append(cmd.Env, "TEST_LOCK_PORTABLE=1")
+ }
+ if err := cmd.Start(); err != nil {
+ t.Fatalf("cannot start child: %v", err)
+ }
+ cmdChan := make(chan childLockCmd)
+ go func() {
+ defer fromChild.Close()
+ defer toChild.Close()
+ inScan := bufio.NewScanner(fromChild)
+ for c := range cmdChan {
+ fmt.Fprintln(toChild, c.op)
+ ok := inScan.Scan()
+ if c.op == "exit" {
+ if ok {
+ c.reply <- errors.New("child did not exit")
+ } else {
+ cmd.Wait()
+ c.reply <- nil
+ }
+ break
+ }
+ if !ok {
+ panic("child exited early")
+ }
+ if errText := inScan.Text(); errText != "" {
+ c.reply <- errors.New(errText)
+ } else {
+ c.reply <- nil
+ }
+ }
+ }()
+ return &childProc{
+ c: cmdChan,
+ proc: cmd.Process,
+ }
+}
diff --git a/vendor/go4.org/lock/lock_unix.go b/vendor/go4.org/lock/lock_unix.go
new file mode 100644
index 0000000..d26056b
--- /dev/null
+++ b/vendor/go4.org/lock/lock_unix.go
@@ -0,0 +1,58 @@
+// +build linux darwin freebsd openbsd netbsd dragonfly solaris
+// +build !appengine
+
+/*
+Copyright 2013 The Go Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package lock
+
+import (
+ "fmt"
+ "io"
+ "os"
+
+ "golang.org/x/sys/unix"
+)
+
+func init() {
+ lockFn = lockFcntl
+}
+
+func lockFcntl(name string) (io.Closer, error) {
+ fi, err := os.Stat(name)
+ if err == nil && fi.Size() > 0 {
+ return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
+ }
+
+ f, err := os.Create(name)
+ if err != nil {
+ return nil, fmt.Errorf("Lock Create of %s failed: %v", name, err)
+ }
+
+ err = unix.FcntlFlock(f.Fd(), unix.F_SETLK, &unix.Flock_t{
+ Type: unix.F_WRLCK,
+ Whence: int16(os.SEEK_SET),
+ Start: 0,
+ Len: 0, // 0 means to lock the entire file.
+ Pid: 0, // only used by F_GETLK
+ })
+
+ if err != nil {
+ f.Close()
+ return nil, fmt.Errorf("Lock FcntlFlock of %s failed: %v", name, err)
+ }
+ return &unlocker{f: f, abs: name}, nil
+}
diff --git a/vendor/go4.org/lock/lock_windows.go b/vendor/go4.org/lock/lock_windows.go
new file mode 100644
index 0000000..4257487
--- /dev/null
+++ b/vendor/go4.org/lock/lock_windows.go
@@ -0,0 +1,78 @@
+/*
+Copyright 2013 The Go Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package lock
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "sync"
+
+ "golang.org/x/sys/windows"
+)
+
+func init() {
+ lockFn = lockWindows
+}
+
+type winUnlocker struct {
+ h windows.Handle
+ abs string
+ // err holds the error returned by Close.
+ err error
+ // once guards the close method call.
+ once sync.Once
+}
+
+func (u *winUnlocker) Close() error {
+ u.once.Do(u.close)
+ return u.err
+}
+
+func (u *winUnlocker) close() {
+ lockmu.Lock()
+ defer lockmu.Unlock()
+ delete(locked, u.abs)
+
+ u.err = windows.CloseHandle(u.h)
+}
+
+func lockWindows(name string) (io.Closer, error) {
+ fi, err := os.Stat(name)
+ if err == nil && fi.Size() > 0 {
+ return nil, fmt.Errorf("can't lock file %q: %s", name, "has non-zero size")
+ }
+
+ handle, err := winCreateEphemeral(name)
+ if err != nil {
+ return nil, fmt.Errorf("creation of lock %s failed: %v", name, err)
+ }
+
+ return &winUnlocker{h: handle, abs: name}, nil
+}
+
+func winCreateEphemeral(name string) (windows.Handle, error) {
+ const (
+ FILE_ATTRIBUTE_TEMPORARY = 0x100
+ FILE_FLAG_DELETE_ON_CLOSE = 0x04000000
+ )
+ handle, err := windows.CreateFile(windows.StringToUTF16Ptr(name), 0, 0, nil, windows.OPEN_ALWAYS, FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE, 0)
+ if err != nil {
+ return 0, err
+ }
+ return handle, nil
+}
diff --git a/vendor/go4.org/media/heif/bmff/bmff.go b/vendor/go4.org/media/heif/bmff/bmff.go
new file mode 100644
index 0000000..126a9da
--- /dev/null
+++ b/vendor/go4.org/media/heif/bmff/bmff.go
@@ -0,0 +1,833 @@
+/*
+Copyright 2018 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package bmff reads ISO BMFF boxes, as used by HEIF, etc.
+//
+// This is not so much as a generic BMFF reader as it is a BMFF reader
+// as needed by HEIF, though that may change in time. For now, only
+// boxes necessary for the go4.org/media/heif package have explicit
+// parsers.
+//
+// This package makes no API compatibility promises; it exists
+// primarily for use by the go4.org/media/heif package.
+package bmff
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "strings"
+)
+
+func NewReader(r io.Reader) *Reader {
+ br, ok := r.(*bufio.Reader)
+ if !ok {
+ br = bufio.NewReader(r)
+ }
+ return &Reader{br: bufReader{Reader: br}}
+}
+
+type Reader struct {
+ br bufReader
+ lastBox Box // or nil
+ noMoreBoxes bool // a box with size 0 (the final box) was seen
+}
+
+type BoxType [4]byte
+
+// Common box types.
+var (
+ TypeFtyp = BoxType{'f', 't', 'y', 'p'}
+ TypeMeta = BoxType{'m', 'e', 't', 'a'}
+)
+
+func (t BoxType) String() string { return string(t[:]) }
+
+func (t BoxType) EqualString(s string) bool {
+ // Could be cleaner, but see ohttps://github.com/golang/go/issues/24765
+ return len(s) == 4 && s[0] == t[0] && s[1] == t[1] && s[2] == t[2] && s[3] == t[3]
+}
+
+type parseFunc func(b box, br *bufio.Reader) (Box, error)
+
+// Box represents a BMFF box.
+type Box interface {
+ Size() int64 // 0 means unknown (will read to end of file)
+ Type() BoxType
+
+ // Parses parses the box, populating the fields
+ // in the returned concrete type.
+ //
+ // If Parse has already been called, Parse returns nil.
+ // If the box type is unknown, the returned error is ErrUnknownBox
+ // and it's guaranteed that no bytes have been read from the box.
+ Parse() (Box, error)
+
+ // Body returns the inner bytes of the box, ignoring the header.
+ // The body may start with the 4 byte header of a "Full Box" if the
+ // box's type derives from a full box. Most users will use Parse
+ // instead.
+ // Body will return a new reader at the beginning of the box if the
+ // outer box has already been parsed.
+ Body() io.Reader
+}
+
+// ErrUnknownBox is returned by Box.Parse for unrecognized box types.
+var ErrUnknownBox = errors.New("heif: unknown box")
+
+type parserFunc func(b *box, br *bufReader) (Box, error)
+
+func boxType(s string) BoxType {
+ if len(s) != 4 {
+ panic("bogus boxType length")
+ }
+ return BoxType{s[0], s[1], s[2], s[3]}
+}
+
+var parsers = map[BoxType]parserFunc{
+ boxType("dinf"): parseDataInformationBox,
+ boxType("dref"): parseDataReferenceBox,
+ boxType("ftyp"): parseFileTypeBox,
+ boxType("hdlr"): parseHandlerBox,
+ boxType("iinf"): parseItemInfoBox,
+ boxType("infe"): parseItemInfoEntry,
+ boxType("iloc"): parseItemLocationBox,
+ boxType("ipco"): parseItemPropertyContainerBox,
+ boxType("ipma"): parseItemPropertyAssociation,
+ boxType("iprp"): parseItemPropertiesBox,
+ boxType("irot"): parseImageRotation,
+ boxType("ispe"): parseImageSpatialExtentsProperty,
+ boxType("meta"): parseMetaBox,
+ boxType("pitm"): parsePrimaryItemBox,
+}
+
+type box struct {
+ size int64 // 0 means unknown, will read to end of file (box container)
+ boxType BoxType
+ body io.Reader
+ parsed Box // if non-nil, the Parsed result
+ slurp []byte // if non-nil, the contents slurped to memory
+}
+
+func (b *box) Size() int64 { return b.size }
+func (b *box) Type() BoxType { return b.boxType }
+
+func (b *box) Body() io.Reader {
+ if b.slurp != nil {
+ return bytes.NewReader(b.slurp)
+ }
+ return b.body
+}
+
+func (b *box) Parse() (Box, error) {
+ if b.parsed != nil {
+ return b.parsed, nil
+ }
+ parser, ok := parsers[b.Type()]
+ if !ok {
+ return nil, ErrUnknownBox
+ }
+ v, err := parser(b, &bufReader{Reader: bufio.NewReader(b.Body())})
+ if err != nil {
+ return nil, err
+ }
+ b.parsed = v
+ return v, nil
+}
+
+type FullBox struct {
+ *box
+ Version uint8
+ Flags uint32 // 24 bits
+}
+
+// ReadBox reads the next box.
+//
+// If the previously read box was not read to completion, ReadBox consumes
+// the rest of its data.
+//
+// At the end, the error is io.EOF.
+func (r *Reader) ReadBox() (Box, error) {
+ if r.noMoreBoxes {
+ return nil, io.EOF
+ }
+ if r.lastBox != nil {
+ if _, err := io.Copy(ioutil.Discard, r.lastBox.Body()); err != nil {
+ return nil, err
+ }
+ }
+ var buf [8]byte
+
+ _, err := io.ReadFull(r.br, buf[:4])
+ if err != nil {
+ return nil, err
+ }
+ box := &box{
+ size: int64(binary.BigEndian.Uint32(buf[:4])),
+ }
+
+ _, err = io.ReadFull(r.br, box.boxType[:]) // 4 more bytes
+ if err != nil {
+ return nil, err
+ }
+
+ // Special cases for size:
+ var remain int64
+ switch box.size {
+ case 1:
+ // 1 means it's actually a 64-bit size, after the type.
+ _, err = io.ReadFull(r.br, buf[:8])
+ if err != nil {
+ return nil, err
+ }
+ box.size = int64(binary.BigEndian.Uint64(buf[:8]))
+ if box.size < 0 {
+ // Go uses int64 for sizes typically, but BMFF uses uint64.
+ // We assume for now that nobody actually uses boxes larger
+ // than int64.
+ return nil, fmt.Errorf("unexpectedly large box %q", box.boxType)
+ }
+ remain = box.size - 2*4 - 8
+ case 0:
+ // 0 means unknown & to read to end of file. No more boxes.
+ r.noMoreBoxes = true
+ default:
+ remain = box.size - 2*4
+ }
+ if remain < 0 {
+ return nil, fmt.Errorf("Box header for %q has size %d, suggesting %d (negative) bytes remain", box.boxType, box.size, remain)
+ }
+ if box.size > 0 {
+ box.body = io.LimitReader(r.br, remain)
+ } else {
+ box.body = r.br
+ }
+ r.lastBox = box
+ return box, nil
+}
+
+// ReadAndParseBox wraps the ReadBox method, ensuring that the read box is of type typ
+// and parses successfully. It returns the parsed box.
+func (r *Reader) ReadAndParseBox(typ BoxType) (Box, error) {
+ box, err := r.ReadBox()
+ if err != nil {
+ return nil, fmt.Errorf("error reading %q box: %v", typ, err)
+ }
+ if box.Type() != typ {
+ return nil, fmt.Errorf("error reading %q box: got box type %q instead", typ, box.Type())
+ }
+ pbox, err := box.Parse()
+ if err != nil {
+ return nil, fmt.Errorf("error parsing read %q box: %v", typ, err)
+ }
+ return pbox, nil
+}
+
+func readFullBox(outer *box, br *bufReader) (fb FullBox, err error) {
+ fb.box = outer
+ // Parse FullBox header.
+ buf, err := br.Peek(4)
+ if err != nil {
+ return FullBox{}, fmt.Errorf("failed to read 4 bytes of FullBox: %v", err)
+ }
+ fb.Version = buf[0]
+ buf[0] = 0
+ fb.Flags = binary.BigEndian.Uint32(buf[:4])
+ br.Discard(4)
+ return fb, nil
+}
+
+type FileTypeBox struct {
+ *box
+ MajorBrand string // 4 bytes
+ MinorVersion string // 4 bytes
+ Compatible []string // all 4 bytes
+}
+
+func parseFileTypeBox(outer *box, br *bufReader) (Box, error) {
+ buf, err := br.Peek(8)
+ if err != nil {
+ return nil, err
+ }
+ ft := &FileTypeBox{
+ box: outer,
+ MajorBrand: string(buf[:4]),
+ MinorVersion: string(buf[4:8]),
+ }
+ br.Discard(8)
+ for {
+ buf, err := br.Peek(4)
+ if err == io.EOF {
+ return ft, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ ft.Compatible = append(ft.Compatible, string(buf[:4]))
+ br.Discard(4)
+ }
+}
+
+type MetaBox struct {
+ FullBox
+ Children []Box
+}
+
+func parseMetaBox(outer *box, br *bufReader) (Box, error) {
+ fb, err := readFullBox(outer, br)
+ if err != nil {
+ return nil, err
+ }
+ mb := &MetaBox{FullBox: fb}
+ return mb, br.parseAppendBoxes(&mb.Children)
+}
+
+func (br *bufReader) parseAppendBoxes(dst *[]Box) error {
+ if br.err != nil {
+ return br.err
+ }
+ boxr := NewReader(br.Reader)
+ for {
+ inner, err := boxr.ReadBox()
+ if err == io.EOF {
+ return nil
+ }
+ if err != nil {
+ br.err = err
+ return err
+ }
+ slurp, err := ioutil.ReadAll(inner.Body())
+ if err != nil {
+ br.err = err
+ return err
+ }
+ inner.(*box).slurp = slurp
+ *dst = append(*dst, inner)
+ }
+}
+
+// ItemInfoEntry represents an "infe" box.
+//
+// TODO: currently only parses Version 2 boxes.
+type ItemInfoEntry struct {
+ FullBox
+
+ ItemID uint16
+ ProtectionIndex uint16
+ ItemType string // always 4 bytes
+
+ Name string
+
+ // If Type == "mime":
+ ContentType string
+ ContentEncoding string
+
+ // If Type == "uri ":
+ ItemURIType string
+}
+
+func parseItemInfoEntry(outer *box, br *bufReader) (Box, error) {
+ fb, err := readFullBox(outer, br)
+ if err != nil {
+ return nil, err
+ }
+ ie := &ItemInfoEntry{FullBox: fb}
+ if fb.Version != 2 {
+ return nil, fmt.Errorf("TODO: found version %d infe box. Only 2 is supported now.", fb.Version)
+ }
+
+ ie.ItemID, _ = br.readUint16()
+ ie.ProtectionIndex, _ = br.readUint16()
+ if !br.ok() {
+ return nil, br.err
+ }
+ buf, err := br.Peek(4)
+ if err != nil {
+ return nil, err
+ }
+ ie.ItemType = string(buf[:4])
+ ie.Name, _ = br.readString()
+
+ switch ie.ItemType {
+ case "mime":
+ ie.ContentType, _ = br.readString()
+ if br.anyRemain() {
+ ie.ContentEncoding, _ = br.readString()
+ }
+ case "uri ":
+ ie.ItemURIType, _ = br.readString()
+ }
+ if !br.ok() {
+ return nil, br.err
+ }
+ return ie, nil
+}
+
+// ItemInfoBox represents an "iinf" box.
+type ItemInfoBox struct {
+ FullBox
+ Count uint16
+ ItemInfos []*ItemInfoEntry
+}
+
+func parseItemInfoBox(outer *box, br *bufReader) (Box, error) {
+ fb, err := readFullBox(outer, br)
+ if err != nil {
+ return nil, err
+ }
+ ib := &ItemInfoBox{FullBox: fb}
+
+ ib.Count, _ = br.readUint16()
+
+ var itemInfos []Box
+ br.parseAppendBoxes(&itemInfos)
+ if br.ok() {
+ for _, box := range itemInfos {
+ pb, err := box.Parse()
+ if err != nil {
+ return nil, fmt.Errorf("error parsing ItemInfoEntry in ItemInfoBox: %v", err)
+ }
+ if iie, ok := pb.(*ItemInfoEntry); ok {
+ ib.ItemInfos = append(ib.ItemInfos, iie)
+ }
+ }
+ }
+ if !br.ok() {
+ return FullBox{}, br.err
+ }
+ return ib, nil
+}
+
+// bufReader adds some HEIF/BMFF-specific methods around a *bufio.Reader.
+type bufReader struct {
+ *bufio.Reader
+ err error // sticky error
+}
+
+// ok reports whether all previous reads have been error-free.
+func (br *bufReader) ok() bool { return br.err == nil }
+
+func (br *bufReader) anyRemain() bool {
+ if br.err != nil {
+ return false
+ }
+ _, err := br.Peek(1)
+ return err == nil
+}
+
+func (br *bufReader) readUintN(bits uint8) (uint64, error) {
+ if br.err != nil {
+ return 0, br.err
+ }
+ if bits == 0 {
+ return 0, nil
+ }
+ nbyte := bits / 8
+ buf, err := br.Peek(int(nbyte))
+ if err != nil {
+ br.err = err
+ return 0, err
+ }
+ defer br.Discard(int(nbyte))
+ switch bits {
+ case 8:
+ return uint64(buf[0]), nil
+ case 16:
+ return uint64(binary.BigEndian.Uint16(buf[:2])), nil
+ case 32:
+ return uint64(binary.BigEndian.Uint32(buf[:4])), nil
+ case 64:
+ return binary.BigEndian.Uint64(buf[:8]), nil
+ default:
+ br.err = fmt.Errorf("invalid uintn read size")
+ return 0, br.err
+ }
+}
+
+func (br *bufReader) readUint8() (uint8, error) {
+ if br.err != nil {
+ return 0, br.err
+ }
+ v, err := br.ReadByte()
+ if err != nil {
+ br.err = err
+ return 0, err
+ }
+ return v, nil
+}
+
+func (br *bufReader) readUint16() (uint16, error) {
+ if br.err != nil {
+ return 0, br.err
+ }
+ buf, err := br.Peek(2)
+ if err != nil {
+ br.err = err
+ return 0, err
+ }
+ v := binary.BigEndian.Uint16(buf[:2])
+ br.Discard(2)
+ return v, nil
+}
+
+func (br *bufReader) readUint32() (uint32, error) {
+ if br.err != nil {
+ return 0, br.err
+ }
+ buf, err := br.Peek(4)
+ if err != nil {
+ br.err = err
+ return 0, err
+ }
+ v := binary.BigEndian.Uint32(buf[:4])
+ br.Discard(4)
+ return v, nil
+}
+
+func (br *bufReader) readString() (string, error) {
+ if br.err != nil {
+ return "", br.err
+ }
+ s0, err := br.ReadString(0)
+ if err != nil {
+ br.err = err
+ return "", err
+ }
+ s := strings.TrimSuffix(s0, "\x00")
+ if len(s) == len(s0) {
+ err = fmt.Errorf("unexpected non-null terminated string")
+ br.err = err
+ return "", err
+ }
+ return s, nil
+}
+
+// HEIF: ipco
+type ItemPropertyContainerBox struct {
+ *box
+ Properties []Box // of ItemProperty or ItemFullProperty
+}
+
+func parseItemPropertyContainerBox(outer *box, br *bufReader) (Box, error) {
+ ipc := &ItemPropertyContainerBox{box: outer}
+ return ipc, br.parseAppendBoxes(&ipc.Properties)
+}
+
+// HEIF: iprp
+type ItemPropertiesBox struct {
+ *box
+ PropertyContainer *ItemPropertyContainerBox
+ Associations []*ItemPropertyAssociation // at least 1
+}
+
+func parseItemPropertiesBox(outer *box, br *bufReader) (Box, error) {
+ ip := &ItemPropertiesBox{
+ box: outer,
+ }
+
+ var boxes []Box
+ err := br.parseAppendBoxes(&boxes)
+ if err != nil {
+ return nil, err
+ }
+ if len(boxes) < 2 {
+ return nil, fmt.Errorf("expect at least 2 boxes in children; got 0")
+ }
+
+ cb, err := boxes[0].Parse()
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse first box, %q: %v", boxes[0].Type(), err)
+ }
+
+ var ok bool
+ ip.PropertyContainer, ok = cb.(*ItemPropertyContainerBox)
+ if !ok {
+ return nil, fmt.Errorf("unexpected type %T for ItemPropertieBox.PropertyContainer", cb)
+ }
+
+ // Association boxes
+ ip.Associations = make([]*ItemPropertyAssociation, 0, len(boxes)-1)
+ for _, box := range boxes[1:] {
+ boxp, err := box.Parse()
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse association box: %v", err)
+ }
+ ipa, ok := boxp.(*ItemPropertyAssociation)
+ if !ok {
+ return nil, fmt.Errorf("unexpected box %q instead of ItemPropertyAssociation", boxp.Type())
+ }
+ ip.Associations = append(ip.Associations, ipa)
+ }
+ return ip, nil
+}
+
+type ItemPropertyAssociation struct {
+ FullBox
+ EntryCount uint32
+ Entries []ItemPropertyAssociationItem
+}
+
+// not a box
+type ItemProperty struct {
+ Essential bool
+ Index uint16
+}
+
+// not a box
+type ItemPropertyAssociationItem struct {
+ ItemID uint32
+ AssociationsCount int // as declared
+ Associations []ItemProperty // as parsed
+}
+
+func parseItemPropertyAssociation(outer *box, br *bufReader) (Box, error) {
+ fb, err := readFullBox(outer, br)
+ if err != nil {
+ return nil, err
+ }
+ ipa := &ItemPropertyAssociation{FullBox: fb}
+ count, _ := br.readUint32()
+ ipa.EntryCount = count
+
+ for i := uint64(0); i < uint64(count) && br.ok(); i++ {
+ var itemID uint32
+ if fb.Version < 1 {
+ itemID16, _ := br.readUint16()
+ itemID = uint32(itemID16)
+ } else {
+ itemID, _ = br.readUint32()
+ }
+ assocCount, _ := br.readUint8()
+ ipai := ItemPropertyAssociationItem{
+ ItemID: itemID,
+ AssociationsCount: int(assocCount),
+ }
+ for j := 0; j < int(assocCount) && br.ok(); j++ {
+ first, _ := br.readUint8()
+ essential := first&(1<<7) != 0
+ first &^= byte(1 << 7)
+
+ var index uint16
+ if fb.Flags&1 != 0 {
+ second, _ := br.readUint8()
+ index = uint16(first)<<8 | uint16(second)
+ } else {
+ index = uint16(first)
+ }
+ ipai.Associations = append(ipai.Associations, ItemProperty{
+ Essential: essential,
+ Index: index,
+ })
+ }
+ ipa.Entries = append(ipa.Entries, ipai)
+ }
+ if !br.ok() {
+ return nil, br.err
+ }
+ return ipa, nil
+}
+
+type ImageSpatialExtentsProperty struct {
+ FullBox
+ ImageWidth uint32
+ ImageHeight uint32
+}
+
+func parseImageSpatialExtentsProperty(outer *box, br *bufReader) (Box, error) {
+ fb, err := readFullBox(outer, br)
+ if err != nil {
+ return nil, err
+ }
+ w, err := br.readUint32()
+ if err != nil {
+ return nil, err
+ }
+ h, err := br.readUint32()
+ if err != nil {
+ return nil, err
+ }
+ return &ImageSpatialExtentsProperty{
+ FullBox: fb,
+ ImageWidth: w,
+ ImageHeight: h,
+ }, nil
+}
+
+type OffsetLength struct {
+ Offset, Length uint64
+}
+
+// not a box
+type ItemLocationBoxEntry struct {
+ ItemID uint16
+ ConstructionMethod uint8 // actually uint4
+ DataReferenceIndex uint16
+ BaseOffset uint64 // uint32 or uint64, depending on encoding
+ ExtentCount uint16
+ Extents []OffsetLength
+}
+
+// box "iloc"
+type ItemLocationBox struct {
+ FullBox
+
+ offsetSize, lengthSize, baseOffsetSize, indexSize uint8 // actually uint4
+
+ ItemCount uint16
+ Items []ItemLocationBoxEntry
+}
+
+func parseItemLocationBox(outer *box, br *bufReader) (Box, error) {
+ fb, err := readFullBox(outer, br)
+ if err != nil {
+ return nil, err
+ }
+ ilb := &ItemLocationBox{
+ FullBox: fb,
+ }
+ buf, err := br.Peek(4)
+ if err != nil {
+ return nil, err
+ }
+ ilb.offsetSize = buf[0] >> 4
+ ilb.lengthSize = buf[0] & 15
+ ilb.baseOffsetSize = buf[1] >> 4
+ if fb.Version > 0 { // version 1
+ ilb.indexSize = buf[1] & 15
+ }
+
+ ilb.ItemCount = binary.BigEndian.Uint16(buf[2:4])
+ br.Discard(4)
+
+ for i := 0; br.ok() && i < int(ilb.ItemCount); i++ {
+ var ent ItemLocationBoxEntry
+ ent.ItemID, _ = br.readUint16()
+ if fb.Version > 0 { // version 1
+ cmeth, _ := br.readUint16()
+ ent.ConstructionMethod = byte(cmeth & 15)
+ }
+ ent.DataReferenceIndex, _ = br.readUint16()
+ if br.ok() && ilb.baseOffsetSize > 0 {
+ br.Discard(int(ilb.baseOffsetSize) / 8)
+ }
+ ent.ExtentCount, _ = br.readUint16()
+ for j := 0; br.ok() && j < int(ent.ExtentCount); j++ {
+ var ol OffsetLength
+ ol.Offset, _ = br.readUintN(ilb.offsetSize * 8)
+ ol.Length, _ = br.readUintN(ilb.lengthSize * 8)
+ if br.err != nil {
+ return nil, br.err
+ }
+ ent.Extents = append(ent.Extents, ol)
+ }
+ ilb.Items = append(ilb.Items, ent)
+ }
+ if !br.ok() {
+ return nil, br.err
+ }
+ return ilb, nil
+}
+
+// a "hdlr" box.
+type HandlerBox struct {
+ FullBox
+ HandlerType string // always 4 bytes; usually "pict" for iOS Camera images
+ Name string
+}
+
+func parseHandlerBox(gen *box, br *bufReader) (Box, error) {
+ fb, err := readFullBox(gen, br)
+ if err != nil {
+ return nil, err
+ }
+ hb := &HandlerBox{
+ FullBox: fb,
+ }
+ buf, err := br.Peek(20)
+ if err != nil {
+ return nil, err
+ }
+ hb.HandlerType = string(buf[4:8])
+ br.Discard(20)
+
+ hb.Name, _ = br.readString()
+ return hb, br.err
+}
+
+// a "dinf" box
+type DataInformationBox struct {
+ *box
+ Children []Box
+}
+
+func parseDataInformationBox(gen *box, br *bufReader) (Box, error) {
+ dib := &DataInformationBox{box: gen}
+ return dib, br.parseAppendBoxes(&dib.Children)
+}
+
+// a "dref" box.
+type DataReferenceBox struct {
+ FullBox
+ EntryCount uint32
+ Children []Box
+}
+
+func parseDataReferenceBox(gen *box, br *bufReader) (Box, error) {
+ fb, err := readFullBox(gen, br)
+ if err != nil {
+ return nil, err
+ }
+ drb := &DataReferenceBox{FullBox: fb}
+ drb.EntryCount, _ = br.readUint32()
+ return drb, br.parseAppendBoxes(&drb.Children)
+}
+
+// "pitm" box
+type PrimaryItemBox struct {
+ FullBox
+ ItemID uint16
+}
+
+func parsePrimaryItemBox(gen *box, br *bufReader) (Box, error) {
+ fb, err := readFullBox(gen, br)
+ if err != nil {
+ return nil, err
+ }
+ pib := &PrimaryItemBox{FullBox: fb}
+ pib.ItemID, _ = br.readUint16()
+ if !br.ok() {
+ return nil, br.err
+ }
+ return pib, nil
+}
+
+// ImageRotation is a HEIF "irot" rotation property.
+type ImageRotation struct {
+ *box
+ Angle uint8 // 1 means 90 degrees counter-clockwise, 2 means 180 counter-clockwise
+}
+
+func parseImageRotation(gen *box, br *bufReader) (Box, error) {
+ v, err := br.readUint8()
+ if err != nil {
+ return nil, err
+ }
+ return &ImageRotation{box: gen, Angle: v & 3}, nil
+}
diff --git a/vendor/go4.org/media/heif/dumpheif/dumpheif.go b/vendor/go4.org/media/heif/dumpheif/dumpheif.go
new file mode 100644
index 0000000..44c6b8c
--- /dev/null
+++ b/vendor/go4.org/media/heif/dumpheif/dumpheif.go
@@ -0,0 +1,200 @@
+/*
+Copyright 2018 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// The dumpheif program dumps the structure and metadata of a HEIF file.
+//
+// It exists purely for debugging the go4.org/media/heif and
+// go4.org/media/heif/bmff packages; it makes no backwards
+// compatibility promises.
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "strings"
+
+ "github.com/rwcarlsen/goexif/exif"
+ "github.com/rwcarlsen/goexif/tiff"
+
+ "go4.org/media/heif"
+ "go4.org/media/heif/bmff"
+)
+
+var (
+ exifItemID uint16
+ exifLoc bmff.ItemLocationBoxEntry
+)
+
+func main() {
+ flag.Parse()
+ if flag.NArg() != 1 {
+ fmt.Fprintf(os.Stderr, "usage: dumpheif \n")
+ os.Exit(1)
+ }
+ f, err := os.Open(flag.Arg(0))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer f.Close()
+
+ hf := heif.Open(f)
+
+ it, err := hf.PrimaryItem()
+ if err != nil {
+ log.Fatalf("PrimaryItem: %v", err)
+ }
+ fmt.Printf("primary item: %v\n", it.ID)
+
+ width, height, ok := it.SpatialExtents()
+ if ok {
+ fmt.Printf("spatial extents: %d x %d\n", width, height)
+ }
+ fmt.Printf("properties:\n")
+ for _, prop := range it.Properties {
+ fmt.Printf("\t%q: %#v\n", prop.Type(), prop)
+ }
+ if len(it.Properties) == 0 {
+ fmt.Printf("\t(no properties)\n")
+ }
+
+ if ex, err := hf.EXIF(); err == nil {
+ fmt.Printf("EXIF dump:\n")
+ ex, err := exif.Decode(bytes.NewReader(ex))
+ if err != nil {
+ log.Fatalf("EXIF decode: %v", err)
+ }
+ ex.Walk(exifWalkFunc(func(name exif.FieldName, tag *tiff.Tag) error {
+ fmt.Printf("\t%v = %v\n", name, tag)
+ return nil
+ }))
+ fmt.Printf("\n")
+ }
+
+ fmt.Printf("BMFF boxes:\n")
+ r := bmff.NewReader(f)
+ for {
+ box, err := r.ReadBox()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ log.Fatalf("ReadBox: %v", err)
+ }
+ dumpBox(box, 0)
+ }
+
+}
+
+type exifWalkFunc func(exif.FieldName, *tiff.Tag) error
+
+func (f exifWalkFunc) Walk(name exif.FieldName, tag *tiff.Tag) error {
+ return f(name, tag)
+}
+
+func dumpBox(box bmff.Box, depth int) {
+ indent := strings.Repeat(" ", depth)
+ fmt.Printf("%sBox: type %q, size %v\n", indent, box.Type(), box.Size())
+
+ box2, err := box.Parse()
+ if err == bmff.ErrUnknownBox {
+ slurp, err := ioutil.ReadAll(box.Body())
+ if err != nil {
+ log.Fatalf("%sreading body: %v", indent, err)
+ }
+ if len(slurp) < 5000 {
+ fmt.Printf("%s- contents: %q\n", indent, slurp)
+ } else {
+ fmt.Printf("%s- contents: (... %d bytes, starting with %q ...)\n", indent, len(slurp), slurp[:100])
+ }
+ return
+ }
+ if err != nil {
+ slurp, _ := ioutil.ReadAll(box.Body())
+ log.Fatalf("Parse box type %q: %v; slurp: %q", box.Type(), err, slurp)
+ }
+
+ switch v := box2.(type) {
+ case *bmff.FileTypeBox, *bmff.HandlerBox, *bmff.PrimaryItemBox:
+ fmt.Printf("%s- %T: %+v\n", indent, v, v)
+ case *bmff.MetaBox:
+ fmt.Printf("%s- %T, %d children:\n", indent, v, len(v.Children))
+ for _, child := range v.Children {
+ dumpBox(child, depth+1)
+ }
+ case *bmff.ItemInfoBox:
+ //slurp, _ := ioutil.ReadAll(box.Body())
+ //fmt.Printf("%s- %T raw: %q\n", indent, v, slurp)
+ fmt.Printf("%s- %T, %d children (%d in slice):\n", indent, v, v.Count, len(v.ItemInfos))
+ for _, child := range v.ItemInfos {
+ dumpBox(child, depth+1)
+ }
+ case *bmff.ItemInfoEntry:
+ fmt.Printf("%s- %T, %+v\n", indent, v, v)
+ if v.ItemType == "Exif" {
+ exifItemID = v.ItemID
+ }
+ case *bmff.ItemPropertiesBox:
+ fmt.Printf("%s- %T\n", indent, v)
+ if v.PropertyContainer != nil {
+ dumpBox(v.PropertyContainer, depth+1)
+ }
+ for _, child := range v.Associations {
+ dumpBox(child, depth+1)
+ }
+ case *bmff.ItemPropertyAssociation:
+ fmt.Printf("%s- %T: %d declared entries, %d parsed:\n", indent, v, v.EntryCount, len(v.Entries))
+ for _, ai := range v.Entries {
+ fmt.Printf("%s for Item ID %d, %d associations declared, %d parsed:\n", indent, ai.ItemID, ai.AssociationsCount, len(ai.Associations))
+ for _, ass := range ai.Associations {
+ fmt.Printf("%s index: %d, essential: %v\n", indent, ass.Index, ass.Essential)
+ }
+ }
+ case *bmff.DataInformationBox:
+ fmt.Printf("%s- %T\n", indent, v)
+ for _, child := range v.Children {
+ dumpBox(child, depth+1)
+ }
+ case *bmff.DataReferenceBox:
+ fmt.Printf("%s- %T\n", indent, v)
+ for _, child := range v.Children {
+ dumpBox(child, depth+1)
+ }
+ case *bmff.ItemPropertyContainerBox:
+ fmt.Printf("%s- %T\n", indent, v)
+ for _, child := range v.Properties {
+ dumpBox(child, depth+1)
+ }
+ case *bmff.ItemLocationBox:
+ fmt.Printf("%s- %T: %d items declared, %d parsed:\n", indent, v, v.ItemCount, len(v.Items))
+ for _, lbe := range v.Items {
+ fmt.Printf("%s %+v\n", indent, lbe)
+ if exifItemID != 0 && lbe.ItemID == exifItemID {
+ exifLoc = lbe
+ }
+ }
+
+ case *bmff.ImageSpatialExtentsProperty:
+ fmt.Printf("%s- %T dimensions: %d x %d\n", indent, v, v.ImageWidth, v.ImageHeight)
+ default:
+ fmt.Printf("%s- gotype: %T\n", indent, box2)
+ }
+
+}
diff --git a/vendor/go4.org/media/heif/heif.go b/vendor/go4.org/media/heif/heif.go
new file mode 100644
index 0000000..cb6da04
--- /dev/null
+++ b/vendor/go4.org/media/heif/heif.go
@@ -0,0 +1,292 @@
+/*
+Copyright 2018 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package heif reads HEIF containers, as found in Apple HEIC/HEVC images.
+// This package does not decode images; it only reads the metadata.
+//
+// This package is a work in progress and makes no API compatibility
+// promises.
+package heif
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "log"
+
+ "go4.org/media/heif/bmff"
+)
+
+// File represents a HEIF file.
+//
+// Methods on File should not be called concurrently.
+type File struct {
+ ra io.ReaderAt
+ primary *Item
+
+ // Populated lazily, by getMeta:
+ metaErr error
+ meta *BoxMeta
+}
+
+// BoxMeta contains the low-level BMFF metadata boxes.
+type BoxMeta struct {
+ FileType *bmff.FileTypeBox
+ Handler *bmff.HandlerBox
+ PrimaryItem *bmff.PrimaryItemBox
+ ItemInfo *bmff.ItemInfoBox
+ Properties *bmff.ItemPropertiesBox
+ ItemLocation *bmff.ItemLocationBox
+}
+
+// EXIFItemID returns the item ID of the EXIF part, or 0 if not found.
+func (m *BoxMeta) EXIFItemID() uint32 {
+ if m.ItemInfo == nil {
+ return 0
+ }
+ for _, ife := range m.ItemInfo.ItemInfos {
+ if ife.ItemType == "Exif" {
+ return uint32(ife.ItemID)
+ }
+ }
+ return 0
+}
+
+// Item represents an item in a HEIF file.
+type Item struct {
+ f *File
+
+ ID uint32
+ Info *bmff.ItemInfoEntry
+ Location *bmff.ItemLocationBoxEntry // location in file
+ Properties []bmff.Box
+}
+
+// SpatialExtents returns the item's spatial extents property values, if present,
+// not correcting from any camera rotation metadata.
+func (it *Item) SpatialExtents() (width, height int, ok bool) {
+ for _, p := range it.Properties {
+ if p, ok := p.(*bmff.ImageSpatialExtentsProperty); ok {
+ return int(p.ImageWidth), int(p.ImageHeight), true
+ }
+ }
+ return
+}
+
+// Rotations returns the number of 90 degree rotations counter-clockwise that this
+// image should be rendered at, in the range [0,3].
+func (it *Item) Rotations() int {
+ for _, p := range it.Properties {
+ if p, ok := p.(*bmff.ImageRotation); ok {
+ return int(p.Angle)
+ }
+ }
+ return 0
+}
+
+// VisualDimensions returns the item's width and height after correcting
+// for any rotations.
+func (it *Item) VisualDimensions() (width, height int, ok bool) {
+ width, height, ok = it.SpatialExtents()
+ for i := 0; i < it.Rotations(); i++ {
+ width, height = height, width
+ }
+ return
+}
+
+// TODO: add HEIF imir (mirroring) accessor, like Image.SpatialExtents.
+
+// Open returns a handle to access a HEIF file.
+func Open(f io.ReaderAt) *File {
+ return &File{ra: f}
+}
+
+// ErrNoEXIF is returned by File.EXIF when a file does not contain an EXIF item.
+var ErrNoEXIF = errors.New("heif: no EXIF found")
+
+// ErrUnknownItem is returned by File.ItemByID for unknown items.
+var ErrUnknownItem = errors.New("heif: unknown item")
+
+// EXIF returns the raw EXIF data from the file.
+// The error is ErrNoEXIF if the file did not contain EXIF.
+//
+// The raw EXIF data can be parsed by the
+// github.com/rwcarlsen/goexif/exif package's Decode function.
+func (f *File) EXIF() ([]byte, error) {
+ meta, err := f.getMeta()
+ if err != nil {
+ return nil, err
+ }
+ exifID := meta.EXIFItemID()
+ if exifID == 0 {
+ return nil, ErrNoEXIF
+ }
+ it, err := f.ItemByID(exifID)
+ if err != nil {
+ return nil, err
+ }
+ if it.Location == nil {
+ return nil, errors.New("heif: file said it contained EXIF, but didn't say where")
+ }
+ if n := len(it.Location.Extents); n != 1 {
+ return nil, fmt.Errorf("heif: expected 1 EXIF section, saw %d", n)
+ }
+ offLen := it.Location.Extents[0]
+ const maxSize = 20 << 10 // 20MB of EXIF seems excessive; cap it for sanity
+ if offLen.Length > maxSize {
+ return nil, fmt.Errorf("heif: declared EXIF size %d exceeds threshold of %d bytes", offLen.Length, maxSize)
+ }
+ buf := make([]byte, offLen.Length-4)
+ n, err := f.ra.ReadAt(buf, int64(offLen.Offset)+4) // TODO: why 4? did I miss something?
+ if err != nil {
+ log.Printf("Read %d bytes + %v: %q", n, err, buf)
+ return nil, err
+ }
+ return buf, nil
+}
+
+func (f *File) setMetaErr(err error) error {
+ if f.metaErr != nil {
+ f.metaErr = err
+ }
+ return err
+}
+
+func (f *File) getMeta() (*BoxMeta, error) {
+ if f.metaErr != nil {
+ return nil, f.metaErr
+ }
+ if f.meta != nil {
+ return f.meta, nil
+ }
+ const assumedMaxSize = 5 << 40 // arbitrary
+ sr := io.NewSectionReader(f.ra, 0, assumedMaxSize)
+ bmr := bmff.NewReader(sr)
+
+ meta := &BoxMeta{}
+
+ pbox, err := bmr.ReadAndParseBox(bmff.TypeFtyp)
+ if err != nil {
+ return nil, f.setMetaErr(err)
+ }
+ meta.FileType = pbox.(*bmff.FileTypeBox)
+
+ pbox, err = bmr.ReadAndParseBox(bmff.TypeMeta)
+ if err != nil {
+ return nil, f.setMetaErr(err)
+ }
+ metabox := pbox.(*bmff.MetaBox)
+
+ for _, box := range metabox.Children {
+ boxp, err := box.Parse()
+ if err == bmff.ErrUnknownBox {
+ continue
+ }
+ if err != nil {
+ return nil, f.setMetaErr(err)
+ }
+ switch v := boxp.(type) {
+ case *bmff.HandlerBox:
+ meta.Handler = v
+ case *bmff.PrimaryItemBox:
+ meta.PrimaryItem = v
+ case *bmff.ItemInfoBox:
+ meta.ItemInfo = v
+ case *bmff.ItemPropertiesBox:
+ meta.Properties = v
+ case *bmff.ItemLocationBox:
+ meta.ItemLocation = v
+ }
+ }
+
+ f.meta = meta
+ return f.meta, nil
+}
+
+// PrimaryItem returns the HEIF file's primary item.
+func (f *File) PrimaryItem() (*Item, error) {
+ meta, err := f.getMeta()
+ if err != nil {
+ return nil, err
+ }
+ if meta.PrimaryItem == nil {
+ return nil, errors.New("heif: HEIF file lacks primary item box")
+ }
+ return f.ItemByID(uint32(meta.PrimaryItem.ItemID))
+}
+
+// ItemByID by returns the file's Item of a given ID.
+// If the ID is known, the returned error is ErrUnknownItem.
+func (f *File) ItemByID(id uint32) (*Item, error) {
+ meta, err := f.getMeta()
+ if err != nil {
+ return nil, err
+ }
+ it := &Item{
+ f: f,
+ ID: id,
+ }
+ if meta.ItemLocation != nil {
+ for _, ilbe := range meta.ItemLocation.Items {
+ if uint32(ilbe.ItemID) == id {
+ shallowCopy := ilbe
+ it.Location = &shallowCopy
+ }
+ }
+ }
+ if meta.ItemInfo != nil {
+ for _, iie := range meta.ItemInfo.ItemInfos {
+ if uint32(iie.ItemID) == id {
+ it.Info = iie
+ }
+ }
+ }
+ if it.Info == nil {
+ return nil, ErrUnknownItem
+ }
+ if meta.Properties != nil {
+ allProps := meta.Properties.PropertyContainer.Properties
+ for _, ipa := range meta.Properties.Associations {
+ // TODO: I've never seen a file with more than
+ // top-level ItemPropertyAssociation box, but
+ // apparently they can exist with different
+ // versions/flags. For now we just merge them
+ // all together, but that's not really right.
+ // So for now, just bail once a previous loop
+ // found anything.
+ if len(it.Properties) > 0 {
+ break
+ }
+
+ for _, ipai := range ipa.Entries {
+ if ipai.ItemID != id {
+ continue
+ }
+ for _, ass := range ipai.Associations {
+ if ass.Index != 0 && int(ass.Index) <= len(allProps) {
+ box := allProps[ass.Index-1]
+ boxp, err := box.Parse()
+ if err == nil {
+ box = boxp
+ }
+ it.Properties = append(it.Properties, box)
+ }
+ }
+ }
+ }
+ }
+ return it, nil
+}
diff --git a/vendor/go4.org/media/heif/heif_test.go b/vendor/go4.org/media/heif/heif_test.go
new file mode 100644
index 0000000..61ba7fc
--- /dev/null
+++ b/vendor/go4.org/media/heif/heif_test.go
@@ -0,0 +1,113 @@
+package heif
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/rwcarlsen/goexif/exif"
+ "github.com/rwcarlsen/goexif/tiff"
+)
+
+func TestAll(t *testing.T) {
+ f, err := os.Open("testdata/park.heic")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+ h := Open(f)
+
+ // meta
+ _, err = h.getMeta()
+ if err != nil {
+ t.Fatalf("getMeta: %v", err)
+ }
+
+ it, err := h.PrimaryItem()
+ if err != nil {
+ t.Fatalf("PrimaryItem: %v", err)
+ }
+ if want := uint32(49); it.ID != want {
+ t.Errorf("PrimaryIem ID = %v; want %v", it.ID, want)
+ }
+ if it.Location == nil {
+ t.Errorf("Item.Location is nil")
+ }
+ if it.Info == nil {
+ t.Errorf("Item.Info is nil")
+ }
+ if len(it.Properties) == 0 {
+ t.Errorf("Item.Properties is empty")
+ }
+ for _, prop := range it.Properties {
+ t.Logf(" property: %q, %#v", prop.Type(), prop)
+ }
+ if w, h, ok := it.SpatialExtents(); !ok || w == 0 || h == 0 {
+ t.Errorf("no spatial extents found")
+ } else {
+ t.Logf("dimensions: %v x %v", w, h)
+ }
+
+ // exif
+ exbuf, err := h.EXIF()
+ if err != nil {
+ t.Errorf("EXIF: %v", err)
+ } else {
+ const magic = "Exif\x00\x00"
+ if !bytes.HasPrefix(exbuf, []byte(magic)) {
+ t.Errorf("Exif buffer doesn't start with %q: got %q", magic, exbuf)
+ }
+ x, err := exif.Decode(bytes.NewReader(exbuf))
+ if err != nil {
+ t.Fatalf("EXIF decode: %v", err)
+ }
+ got := map[string]string{}
+ if err := x.Walk(walkFunc(func(name exif.FieldName, tag *tiff.Tag) error {
+ got[fmt.Sprint(name)] = fmt.Sprint(tag)
+ return nil
+ })); err != nil {
+ t.Fatalf("EXIF walk: %v", err)
+ }
+ if g, w := len(got), 56; g < w {
+ t.Errorf("saw %v EXIF tags; want at least %v", g, w)
+ }
+ if g, w := got["GPSLongitude"], `["122/1","21/1","3776/100"]`; g != w {
+ t.Errorf("GPSLongitude = %#q; want %#q", g, w)
+ }
+
+ }
+}
+
+func TestRotations(t *testing.T) {
+ f, err := os.Open("testdata/rotate.heic")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+ h := Open(f)
+ it, err := h.PrimaryItem()
+ if err != nil {
+ t.Fatalf("PrimaryItem: %v", err)
+ }
+ if r := it.Rotations(); r != 3 {
+ t.Errorf("Rotations = %v; want %v", r, 3)
+ }
+ sw, sh, ok := it.SpatialExtents()
+ if !ok {
+ t.Fatalf("expected spatial extents")
+ }
+ vw, vh, ok := it.VisualDimensions()
+ if !ok {
+ t.Fatalf("expected visual dimensions")
+ }
+ if vw != sh || vh != sw {
+ t.Errorf("visual dimensions = %v, %v; want %v, %v", vw, vh, sh, sw)
+ }
+}
+
+type walkFunc func(exif.FieldName, *tiff.Tag) error
+
+func (f walkFunc) Walk(name exif.FieldName, tag *tiff.Tag) error {
+ return f(name, tag)
+}
diff --git a/vendor/go4.org/media/heif/testdata/park.heic b/vendor/go4.org/media/heif/testdata/park.heic
new file mode 100644
index 0000000..7e182de
Binary files /dev/null and b/vendor/go4.org/media/heif/testdata/park.heic differ
diff --git a/vendor/go4.org/media/heif/testdata/rotate.heic b/vendor/go4.org/media/heif/testdata/rotate.heic
new file mode 100644
index 0000000..b65c570
Binary files /dev/null and b/vendor/go4.org/media/heif/testdata/rotate.heic differ
diff --git a/vendor/go4.org/net/throttle/throttle.go b/vendor/go4.org/net/throttle/throttle.go
new file mode 100644
index 0000000..2aa77e1
--- /dev/null
+++ b/vendor/go4.org/net/throttle/throttle.go
@@ -0,0 +1,137 @@
+/*
+Copyright 2012 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package throttle provides a net.Listener that returns
+// artificially-delayed connections for testing real-world
+// connectivity.
+package throttle // import "go4.org/net/throttle"
+
+import (
+ "fmt"
+ "net"
+ "sync"
+ "time"
+)
+
+const unitSize = 1400 // read/write chunk size. ~MTU size.
+
+type Rate struct {
+ KBps int // or 0, to not rate-limit bandwidth
+ Latency time.Duration
+}
+
+// byteTime returns the time required for n bytes.
+func (r Rate) byteTime(n int) time.Duration {
+ if r.KBps == 0 {
+ return 0
+ }
+ return time.Duration(float64(n)/1024/float64(r.KBps)) * time.Second
+}
+
+type Listener struct {
+ net.Listener
+ Down Rate // server Writes to Client
+ Up Rate // server Reads from client
+}
+
+func (ln *Listener) Accept() (net.Conn, error) {
+ c, err := ln.Listener.Accept()
+ time.Sleep(ln.Up.Latency)
+ if err != nil {
+ return nil, err
+ }
+ tc := &conn{Conn: c, Down: ln.Down, Up: ln.Up}
+ tc.start()
+ return tc, nil
+}
+
+type nErr struct {
+ n int
+ err error
+}
+
+type writeReq struct {
+ writeAt time.Time
+ p []byte
+ resc chan nErr
+}
+
+type conn struct {
+ net.Conn
+ Down Rate // for reads
+ Up Rate // for writes
+
+ wchan chan writeReq
+ closeOnce sync.Once
+ closeErr error
+}
+
+func (c *conn) start() {
+ c.wchan = make(chan writeReq, 1024)
+ go c.writeLoop()
+}
+
+func (c *conn) writeLoop() {
+ for req := range c.wchan {
+ time.Sleep(req.writeAt.Sub(time.Now()))
+ var res nErr
+ for len(req.p) > 0 && res.err == nil {
+ writep := req.p
+ if len(writep) > unitSize {
+ writep = writep[:unitSize]
+ }
+ n, err := c.Conn.Write(writep)
+ time.Sleep(c.Up.byteTime(len(writep)))
+ res.n += n
+ res.err = err
+ req.p = req.p[n:]
+ }
+ req.resc <- res
+ }
+}
+
+func (c *conn) Close() error {
+ c.closeOnce.Do(func() {
+ err := c.Conn.Close()
+ close(c.wchan)
+ c.closeErr = err
+ })
+ return c.closeErr
+}
+
+func (c *conn) Write(p []byte) (n int, err error) {
+ defer func() {
+ if e := recover(); e != nil {
+ n = 0
+ err = fmt.Errorf("%v", err)
+ return
+ }
+ }()
+ resc := make(chan nErr, 1)
+ c.wchan <- writeReq{time.Now().Add(c.Up.Latency), p, resc}
+ res := <-resc
+ return res.n, res.err
+}
+
+func (c *conn) Read(p []byte) (n int, err error) {
+ const max = 1024
+ if len(p) > max {
+ p = p[:max]
+ }
+ n, err = c.Conn.Read(p)
+ time.Sleep(c.Down.byteTime(n))
+ return
+}
diff --git a/vendor/go4.org/oauthutil/oauth.go b/vendor/go4.org/oauthutil/oauth.go
new file mode 100644
index 0000000..c7948e2
--- /dev/null
+++ b/vendor/go4.org/oauthutil/oauth.go
@@ -0,0 +1,121 @@
+/*
+Copyright 2015 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package oauthutil contains OAuth 2 related utilities.
+package oauthutil // import "go4.org/oauthutil"
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "time"
+
+ "go4.org/wkfs"
+ "golang.org/x/oauth2"
+)
+
+// TitleBarRedirectURL is the OAuth2 redirect URL to use when the authorization
+// code should be returned in the title bar of the browser, with the page text
+// prompting the user to copy the code and paste it in the application.
+const TitleBarRedirectURL = "urn:ietf:wg:oauth:2.0:oob"
+
+// ErrNoAuthCode is returned when Token() has not found any valid cached token
+// and TokenSource does not have an AuthCode for getting a new token.
+var ErrNoAuthCode = errors.New("oauthutil: unspecified TokenSource.AuthCode")
+
+// TokenSource is an implementation of oauth2.TokenSource. It uses CacheFile to store and
+// reuse the the acquired token, and AuthCode to provide the authorization code that will be
+// exchanged for a token otherwise.
+type TokenSource struct {
+ Config *oauth2.Config
+
+ // CacheFile is where the token will be stored JSON-encoded. Any call to Token
+ // first tries to read a valid token from CacheFile.
+ CacheFile string
+
+ // AuthCode provides the authorization code that Token will exchange for a token.
+ // It usually is a way to prompt the user for the code. If CacheFile does not provide
+ // a token and AuthCode is nil, Token returns ErrNoAuthCode.
+ AuthCode func() string
+}
+
+var errExpiredToken = errors.New("expired token")
+
+// cachedToken returns the token saved in cacheFile. It specifically returns
+// errTokenExpired if the token is expired.
+func cachedToken(cacheFile string) (*oauth2.Token, error) {
+ tok := new(oauth2.Token)
+ tokenData, err := wkfs.ReadFile(cacheFile)
+ if err != nil {
+ return nil, err
+ }
+ if err = json.Unmarshal(tokenData, tok); err != nil {
+ return nil, err
+ }
+ if !tok.Valid() {
+ if tok != nil && time.Now().After(tok.Expiry) {
+ return nil, errExpiredToken
+ }
+ return nil, errors.New("invalid token")
+ }
+ return tok, nil
+}
+
+// Token first tries to find a valid token in CacheFile, and otherwise uses
+// Config and AuthCode to fetch a new token. This new token is saved in CacheFile
+// (if not blank). If CacheFile did not provide a token and AuthCode is nil,
+// ErrNoAuthCode is returned.
+func (src TokenSource) Token() (*oauth2.Token, error) {
+ var tok *oauth2.Token
+ var err error
+ if src.CacheFile != "" {
+ tok, err = cachedToken(src.CacheFile)
+ if err == nil {
+ return tok, nil
+ }
+ if err != errExpiredToken {
+ fmt.Printf("Error getting token from %s: %v\n", src.CacheFile, err)
+ }
+ }
+ if src.AuthCode == nil {
+ return nil, ErrNoAuthCode
+ }
+ tok, err = src.Config.Exchange(oauth2.NoContext, src.AuthCode())
+ if err != nil {
+ return nil, fmt.Errorf("could not exchange auth code for a token: %v", err)
+ }
+ if src.CacheFile == "" {
+ return tok, nil
+ }
+ tokenData, err := json.Marshal(&tok)
+ if err != nil {
+ return nil, fmt.Errorf("could not encode token as json: %v", err)
+ }
+ if err := wkfs.WriteFile(src.CacheFile, tokenData, 0600); err != nil {
+ return nil, fmt.Errorf("could not cache token in %v: %v", src.CacheFile, err)
+ }
+ return tok, nil
+}
+
+// NewRefreshTokenSource returns a token source that obtains its initial token
+// based on the provided config and the refresh token.
+func NewRefreshTokenSource(config *oauth2.Config, refreshToken string) oauth2.TokenSource {
+ var noInitialToken *oauth2.Token = nil
+ return oauth2.ReuseTokenSource(noInitialToken, config.TokenSource(
+ oauth2.NoContext, // TODO: maybe accept a context later.
+ &oauth2.Token{RefreshToken: refreshToken},
+ ))
+}
diff --git a/vendor/go4.org/osutil/exec_plan9.go b/vendor/go4.org/osutil/exec_plan9.go
new file mode 100644
index 0000000..8c82a95
--- /dev/null
+++ b/vendor/go4.org/osutil/exec_plan9.go
@@ -0,0 +1,35 @@
+// Copyright 2015 The go4 Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build plan9
+
+package osutil
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "syscall"
+)
+
+func executable() (string, error) {
+ fn := fmt.Sprintf("/proc/%d/text", os.Getpid())
+ f, err := os.Open(fn)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+ p, err := syscall.Fd2path(int(f.Fd()))
+ return filepath.Clean(p), err
+}
diff --git a/vendor/go4.org/osutil/exec_procfs.go b/vendor/go4.org/osutil/exec_procfs.go
new file mode 100644
index 0000000..062861b
--- /dev/null
+++ b/vendor/go4.org/osutil/exec_procfs.go
@@ -0,0 +1,42 @@
+// Copyright 2015 The go4 Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build linux netbsd openbsd dragonfly nacl
+
+package osutil
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+ "runtime"
+)
+
+func executable() (string, error) {
+ var procfn string
+ switch runtime.GOOS {
+ default:
+ return "", errors.New("Executable not implemented for " + runtime.GOOS)
+ case "linux":
+ procfn = "/proc/self/exe"
+ case "netbsd":
+ procfn = "/proc/curproc/exe"
+ case "openbsd":
+ procfn = "/proc/curproc/file"
+ case "dragonfly":
+ procfn = "/proc/curproc/file"
+ }
+ p, err := os.Readlink(procfn)
+ return filepath.Clean(p), err
+}
diff --git a/vendor/go4.org/osutil/exec_solaris_amd64.go b/vendor/go4.org/osutil/exec_solaris_amd64.go
new file mode 100644
index 0000000..1a43157
--- /dev/null
+++ b/vendor/go4.org/osutil/exec_solaris_amd64.go
@@ -0,0 +1,71 @@
+// Copyright 2015 The go4 Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build amd64,solaris
+
+package osutil
+
+import (
+ "os"
+ "syscall"
+ "unsafe"
+)
+
+//go:cgo_import_dynamic libc_getexecname getexecname "libc.so"
+//go:linkname libc_getexecname libc_getexecname
+
+var libc_getexecname uintptr
+
+func getexecname() (path unsafe.Pointer, err error) {
+ r0, _, e1 := syscall.Syscall6(uintptr(unsafe.Pointer(&libc_getexecname)), 0, 0, 0, 0, 0, 0)
+ path = unsafe.Pointer(r0)
+ if e1 != 0 {
+ err = syscall.Errno(e1)
+ }
+ return
+}
+
+func syscallGetexecname() (path string, err error) {
+ ptr, err := getexecname()
+ if err != nil {
+ return "", err
+ }
+ bytes := (*[1 << 29]byte)(ptr)[:]
+ for i, b := range bytes {
+ if b == 0 {
+ return string(bytes[:i]), nil
+ }
+ }
+ panic("unreachable")
+}
+
+var initCwd, initCwdErr = os.Getwd()
+
+func executable() (string, error) {
+ path, err := syscallGetexecname()
+ if err != nil {
+ return path, err
+ }
+ if len(path) > 0 && path[0] != '/' {
+ if initCwdErr != nil {
+ return path, initCwdErr
+ }
+ if len(path) > 2 && path[0:2] == "./" {
+ // skip "./"
+ path = path[2:]
+ }
+ return initCwd + "/" + path, nil
+ }
+ return path, nil
+}
diff --git a/vendor/go4.org/osutil/exec_sysctl.go b/vendor/go4.org/osutil/exec_sysctl.go
new file mode 100644
index 0000000..192b0f0
--- /dev/null
+++ b/vendor/go4.org/osutil/exec_sysctl.go
@@ -0,0 +1,63 @@
+// Copyright 2015 The go4 Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build freebsd darwin
+
+package osutil
+
+import (
+ "os"
+ "path/filepath"
+ "runtime"
+ "syscall"
+ "unsafe"
+)
+
+var cacheWD, cacheWDErr = os.Getwd()
+
+func executable() (string, error) {
+ var mib [4]int32
+ switch runtime.GOOS {
+ case "freebsd":
+ mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
+ case "darwin":
+ mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1}
+ }
+
+ n := uintptr(0)
+ // get length
+ _, _, err := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
+ if err != 0 {
+ return "", err
+ }
+ if n == 0 { // shouldn't happen
+ return "", nil
+ }
+ buf := make([]byte, n)
+ _, _, err = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
+ if err != 0 {
+ return "", err
+ }
+ if n == 0 { // shouldn't happen
+ return "", nil
+ }
+ p := string(buf[:n-1])
+ if !filepath.IsAbs(p) {
+ if cacheWDErr != nil {
+ return p, cacheWDErr
+ }
+ p = filepath.Join(cacheWD, filepath.Clean(p))
+ }
+ return filepath.EvalSymlinks(p)
+}
diff --git a/vendor/go4.org/osutil/exec_test.go b/vendor/go4.org/osutil/exec_test.go
new file mode 100644
index 0000000..0761919
--- /dev/null
+++ b/vendor/go4.org/osutil/exec_test.go
@@ -0,0 +1,94 @@
+// Copyright 2015 The go4 Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package osutil
+
+import (
+ "fmt"
+ "os"
+ osexec "os/exec"
+ "path/filepath"
+ "runtime"
+ "testing"
+)
+
+const executable_EnvVar = "OSTEST_OUTPUT_EXECPATH"
+
+func TestExecutable(t *testing.T) {
+ if runtime.GOOS == "nacl" {
+ t.Skip()
+ }
+ ep, err := Executable()
+ if err != nil {
+ switch goos := runtime.GOOS; goos {
+ case "openbsd": // procfs is not mounted by default
+ t.Skipf("Executable failed on %s: %v, expected", goos, err)
+ }
+ t.Fatalf("Executable failed: %v", err)
+ }
+ // we want fn to be of the form "dir/prog"
+ dir := filepath.Dir(filepath.Dir(ep))
+ fn, err := filepath.Rel(dir, ep)
+ if err != nil {
+ t.Fatalf("filepath.Rel: %v", err)
+ }
+ cmd := &osexec.Cmd{}
+ // make child start with a relative program path
+ cmd.Dir = dir
+ cmd.Path = fn
+ // forge argv[0] for child, so that we can verify we could correctly
+ // get real path of the executable without influenced by argv[0].
+ cmd.Args = []string{"-", "-test.run=XXXX"}
+ cmd.Env = []string{fmt.Sprintf("%s=1", executable_EnvVar)}
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("exec(self) failed: %v", err)
+ }
+ outs := string(out)
+ if !filepath.IsAbs(outs) {
+ t.Fatalf("Child returned %q, want an absolute path", out)
+ }
+ if !sameFile(outs, ep) {
+ t.Fatalf("Child returned %q, not the same file as %q", out, ep)
+ }
+}
+
+func sameFile(fn1, fn2 string) bool {
+ fi1, err := os.Stat(fn1)
+ if err != nil {
+ return false
+ }
+ fi2, err := os.Stat(fn2)
+ if err != nil {
+ return false
+ }
+ return os.SameFile(fi1, fi2)
+}
+
+func init() {
+ if e := os.Getenv(executable_EnvVar); e != "" {
+ // first chdir to another path
+ dir := "/"
+ if runtime.GOOS == "windows" {
+ dir = filepath.VolumeName(".")
+ }
+ os.Chdir(dir)
+ if ep, err := Executable(); err != nil {
+ fmt.Fprint(os.Stderr, "ERROR: ", err)
+ } else {
+ fmt.Fprint(os.Stderr, ep)
+ }
+ os.Exit(0)
+ }
+}
diff --git a/vendor/go4.org/osutil/exec_windows.go b/vendor/go4.org/osutil/exec_windows.go
new file mode 100644
index 0000000..e2d73a1
--- /dev/null
+++ b/vendor/go4.org/osutil/exec_windows.go
@@ -0,0 +1,64 @@
+/*
+Copyright 2015 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package osutil
+
+import (
+ "path/filepath"
+ "syscall"
+ "unsafe"
+)
+
+var (
+ modkernel32 = syscall.MustLoadDLL("kernel32.dll")
+ procGetModuleFileNameW = modkernel32.MustFindProc("GetModuleFileNameW")
+)
+
+func getModuleFileName(handle syscall.Handle) (string, error) {
+ n := uint32(1024)
+ var buf []uint16
+ for {
+ buf = make([]uint16, n)
+ r, err := syscallGetModuleFileName(handle, &buf[0], n)
+ if err != nil {
+ return "", err
+ }
+ if r < n {
+ break
+ }
+ // r == n means n not big enough
+ n += 1024
+ }
+ return syscall.UTF16ToString(buf), nil
+}
+
+func executable() (string, error) {
+ p, err := getModuleFileName(0)
+ return filepath.Clean(p), err
+}
+
+func syscallGetModuleFileName(module syscall.Handle, fn *uint16, len uint32) (n uint32, err error) {
+ r0, _, e1 := syscall.Syscall(procGetModuleFileNameW.Addr(), 3, uintptr(module), uintptr(unsafe.Pointer(fn)), uintptr(len))
+ n = uint32(r0)
+ if n == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
diff --git a/vendor/go4.org/osutil/osutil.go b/vendor/go4.org/osutil/osutil.go
new file mode 100644
index 0000000..fc0dda0
--- /dev/null
+++ b/vendor/go4.org/osutil/osutil.go
@@ -0,0 +1,32 @@
+/*
+Copyright 2015 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package osutil contains os level functions.
+package osutil // import "go4.org/osutil"
+
+// capture executable on package init to work around various os issues if
+// captured after executable has been renamed.
+var execPath, execError = executable()
+
+// Executable returns the path name for the executable that starts the
+// current process. The result is the path that was used to start the
+// current process, but there is no guarantee that the path is still
+// pointing to the correct executable.
+//
+// OpenBSD is currently unsupported.
+func Executable() (string, error) {
+ return execPath, execError
+}
diff --git a/vendor/go4.org/readerutil/bufreaderat.go b/vendor/go4.org/readerutil/bufreaderat.go
new file mode 100644
index 0000000..0e0a95c
--- /dev/null
+++ b/vendor/go4.org/readerutil/bufreaderat.go
@@ -0,0 +1,48 @@
+/*
+Copyright 2018 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package readerutil
+
+import "io"
+
+// NewBufferingReaderAt returns an io.ReaderAt that reads from r as
+// necessary and keeps a copy of all data read in memory.
+func NewBufferingReaderAt(r io.Reader) io.ReaderAt {
+ return &bufReaderAt{r: r}
+}
+
+type bufReaderAt struct {
+ r io.Reader
+ buf []byte
+}
+
+func (br *bufReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
+ endOff := off + int64(len(p))
+ need := endOff - int64(len(br.buf))
+ if need > 0 {
+ buf := make([]byte, need)
+ var rn int
+ rn, err = io.ReadFull(br.r, buf)
+ br.buf = append(br.buf, buf[:rn]...)
+ }
+ if int64(len(br.buf)) >= off {
+ n = copy(p, br.buf[off:])
+ }
+ if n == len(p) {
+ err = nil
+ }
+ return
+}
diff --git a/vendor/go4.org/readerutil/bufreaderat_test.go b/vendor/go4.org/readerutil/bufreaderat_test.go
new file mode 100644
index 0000000..d5fdc14
--- /dev/null
+++ b/vendor/go4.org/readerutil/bufreaderat_test.go
@@ -0,0 +1,70 @@
+/*
+Copyright 2018 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package readerutil
+
+import "testing"
+
+type trackingReader struct {
+ off int
+ reads int
+ readBytes int
+}
+
+func (t *trackingReader) Read(p []byte) (n int, err error) {
+ t.reads++
+ t.readBytes += len(p)
+ for len(p) > 0 {
+ p[0] = '0' + byte(t.off%10)
+ t.off++
+ p = p[1:]
+ n++
+ }
+ return
+
+}
+
+func TestBufferingReaderAt(t *testing.T) {
+ tr := new(trackingReader)
+ ra := NewBufferingReaderAt(tr)
+ for i, tt := range []struct {
+ off int64
+ want string
+ wantReads int
+ wantReadBytes int
+ }{
+ {off: 0, want: "0123456789", wantReads: 1, wantReadBytes: 10},
+ {off: 5, want: "56789", wantReads: 1, wantReadBytes: 10}, // already buffered
+ {off: 6, want: "67890", wantReads: 2, wantReadBytes: 11}, // need 1 more byte
+ {off: 0, want: "0123456789", wantReads: 2, wantReadBytes: 11}, // already buffered
+ } {
+ got := make([]byte, len(tt.want))
+ n, err := ra.ReadAt(got, tt.off)
+ if err != nil || n != len(tt.want) {
+ t.Errorf("step %d: ReadAt = %v, %v; want %v, %v", i, n, err, len(tt.want), nil)
+ continue
+ }
+ if string(got) != tt.want {
+ t.Errorf("step %d: ReadAt read %q; want %q", i, got, tt.want)
+ }
+ if tr.reads != tt.wantReads {
+ t.Errorf("step %d: num reads = %d; want %d", i, tr.reads, tt.wantReads)
+ }
+ if tr.readBytes != tt.wantReadBytes {
+ t.Errorf("step %d: read bytes = %d; want %d", i, tr.reads, tt.wantReads)
+ }
+ }
+}
diff --git a/vendor/go4.org/readerutil/countingreader.go b/vendor/go4.org/readerutil/countingreader.go
new file mode 100644
index 0000000..bc81303
--- /dev/null
+++ b/vendor/go4.org/readerutil/countingreader.go
@@ -0,0 +1,32 @@
+/*
+Copyright 2011 The Go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package readerutil
+
+import "io"
+
+// CountingReader wraps a Reader, incrementing N by the number of
+// bytes read. No locking is performed.
+type CountingReader struct {
+ Reader io.Reader
+ N *int64
+}
+
+func (cr CountingReader) Read(p []byte) (n int, err error) {
+ n, err = cr.Reader.Read(p)
+ *cr.N += int64(n)
+ return
+}
diff --git a/vendor/go4.org/readerutil/fakeseeker.go b/vendor/go4.org/readerutil/fakeseeker.go
new file mode 100644
index 0000000..7dca839
--- /dev/null
+++ b/vendor/go4.org/readerutil/fakeseeker.go
@@ -0,0 +1,70 @@
+/*
+Copyright 2014 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package readerutil
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "os"
+)
+
+// fakeSeeker can seek to the ends but any read not at the current
+// position will fail.
+type fakeSeeker struct {
+ r io.Reader
+ size int64
+
+ fakePos int64
+ realPos int64
+}
+
+// NewFakeSeeker returns a ReadSeeker that can pretend to Seek (based
+// on the provided total size of the reader's content), but any reads
+// will fail if the fake seek position doesn't match reality.
+func NewFakeSeeker(r io.Reader, size int64) io.ReadSeeker {
+ return &fakeSeeker{r: r, size: size}
+}
+
+func (fs *fakeSeeker) Seek(offset int64, whence int) (int64, error) {
+ var newo int64
+ switch whence {
+ default:
+ return 0, errors.New("invalid whence")
+ case os.SEEK_SET:
+ newo = offset
+ case os.SEEK_CUR:
+ newo = fs.fakePos + offset
+ case os.SEEK_END:
+ newo = fs.size + offset
+ }
+ if newo < 0 {
+ return 0, errors.New("negative seek")
+ }
+ fs.fakePos = newo
+ return newo, nil
+}
+
+func (fs *fakeSeeker) Read(p []byte) (n int, err error) {
+ if fs.fakePos != fs.realPos {
+ return 0, fmt.Errorf("attempt to read from fake seek offset %d; real offset is %d", fs.fakePos, fs.realPos)
+ }
+ n, err = fs.r.Read(p)
+ fs.fakePos += int64(n)
+ fs.realPos += int64(n)
+ return
+}
diff --git a/vendor/go4.org/readerutil/fakeseeker_test.go b/vendor/go4.org/readerutil/fakeseeker_test.go
new file mode 100644
index 0000000..510a56a
--- /dev/null
+++ b/vendor/go4.org/readerutil/fakeseeker_test.go
@@ -0,0 +1,55 @@
+/*
+Copyright 2014 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package readerutil
+
+import (
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestFakeSeeker(t *testing.T) {
+ rs := NewFakeSeeker(strings.NewReader("foobar"), 6)
+ if pos, err := rs.Seek(0, os.SEEK_END); err != nil || pos != 6 {
+ t.Fatalf("SEEK_END = %d, %v; want 6, nil", pos, err)
+ }
+ if pos, err := rs.Seek(0, os.SEEK_CUR); err != nil || pos != 6 {
+ t.Fatalf("SEEK_CUR = %d, %v; want 6, nil", pos, err)
+ }
+ if pos, err := rs.Seek(0, os.SEEK_SET); err != nil || pos != 0 {
+ t.Fatalf("SEEK_SET = %d, %v; want 0, nil", pos, err)
+ }
+
+ buf := make([]byte, 3)
+ if n, err := rs.Read(buf); n != 3 || err != nil || string(buf) != "foo" {
+ t.Fatalf("First read = %d, %v (buf = %q); want foo", n, err, buf)
+ }
+ if pos, err := rs.Seek(0, os.SEEK_CUR); err != nil || pos != 3 {
+ t.Fatalf("Seek cur pos after first read = %d, %v; want 3, nil", pos, err)
+ }
+ if n, err := rs.Read(buf); n != 3 || err != nil || string(buf) != "bar" {
+ t.Fatalf("Second read = %d, %v (buf = %q); want foo", n, err, buf)
+ }
+
+ if pos, err := rs.Seek(1, os.SEEK_SET); err != nil || pos != 1 {
+ t.Fatalf("SEEK_SET = %d, %v; want 1, nil", pos, err)
+ }
+ const msg = "attempt to read from fake seek offset"
+ if _, err := rs.Read(buf); err == nil || !strings.Contains(err.Error(), msg) {
+ t.Fatalf("bogus Read after seek = %v; want something containing %q", err, msg)
+ }
+}
diff --git a/vendor/go4.org/readerutil/multireaderat.go b/vendor/go4.org/readerutil/multireaderat.go
new file mode 100644
index 0000000..33d148c
--- /dev/null
+++ b/vendor/go4.org/readerutil/multireaderat.go
@@ -0,0 +1,91 @@
+/*
+Copyright 2016 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package readerutil
+
+import (
+ "io"
+ "sort"
+)
+
+// NewMultiReaderAt is like io.MultiReader but produces a ReaderAt
+// (and Size), instead of just a reader.
+func NewMultiReaderAt(parts ...SizeReaderAt) SizeReaderAt {
+ m := &multiRA{
+ parts: make([]offsetAndSource, 0, len(parts)),
+ }
+ var off int64
+ for _, p := range parts {
+ m.parts = append(m.parts, offsetAndSource{off, p})
+ off += p.Size()
+ }
+ m.size = off
+ return m
+}
+
+type offsetAndSource struct {
+ off int64
+ SizeReaderAt
+}
+
+type multiRA struct {
+ parts []offsetAndSource
+ size int64
+}
+
+func (m *multiRA) Size() int64 { return m.size }
+
+func (m *multiRA) ReadAt(p []byte, off int64) (n int, err error) {
+ wantN := len(p)
+
+ // Skip past the requested offset.
+ skipParts := sort.Search(len(m.parts), func(i int) bool {
+ // This function returns whether parts[i] will
+ // contribute any bytes to our output.
+ part := m.parts[i]
+ return part.off+part.Size() > off
+ })
+ parts := m.parts[skipParts:]
+
+ // How far to skip in the first part.
+ needSkip := off
+ if len(parts) > 0 {
+ needSkip -= parts[0].off
+ }
+
+ for len(parts) > 0 && len(p) > 0 {
+ readP := p
+ partSize := parts[0].Size()
+ if int64(len(readP)) > partSize-needSkip {
+ readP = readP[:partSize-needSkip]
+ }
+ pn, err0 := parts[0].ReadAt(readP, needSkip)
+ if err0 != nil {
+ return n, err0
+ }
+ n += pn
+ p = p[pn:]
+ if int64(pn)+needSkip == partSize {
+ parts = parts[1:]
+ }
+ needSkip = 0
+ }
+
+ if n != wantN {
+ err = io.ErrUnexpectedEOF
+ }
+ return
+}
diff --git a/vendor/go4.org/readerutil/multireaderat_test.go b/vendor/go4.org/readerutil/multireaderat_test.go
new file mode 100644
index 0000000..a736e02
--- /dev/null
+++ b/vendor/go4.org/readerutil/multireaderat_test.go
@@ -0,0 +1,48 @@
+/*
+Copyright 2016 The Go4 Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package readerutil
+
+import (
+ "io"
+ "io/ioutil"
+ "strings"
+ "testing"
+)
+
+func TestMultiReaderAt(t *testing.T) {
+ sra := NewMultiReaderAt(
+ io.NewSectionReader(strings.NewReader("xaaax"), 1, 3),
+ io.NewSectionReader(strings.NewReader("xxbbbbxx"), 2, 3),
+ io.NewSectionReader(strings.NewReader("cccx"), 0, 3),
+ )
+ if sra.Size() != 9 {
+ t.Fatalf("Size = %d; want 9", sra.Size())
+ }
+ const full = "aaabbbccc"
+ for start := 0; start < len(full); start++ {
+ for end := start; end < len(full); end++ {
+ want := full[start:end]
+ got, err := ioutil.ReadAll(io.NewSectionReader(sra, int64(start), int64(end-start)))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if string(got) != want {
+ t.Errorf("for start=%d, end=%d: ReadAll = %q; want %q", start, end, got, want)
+ }
+ }
+ }
+}
diff --git a/vendor/go4.org/readerutil/readersize.go b/vendor/go4.org/readerutil/readersize.go
new file mode 100644
index 0000000..7e2aa18
--- /dev/null
+++ b/vendor/go4.org/readerutil/readersize.go
@@ -0,0 +1,58 @@
+/*
+Copyright 2012 The Go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package readerutil provides and operates on io.Readers.
+package readerutil // import "go4.org/readerutil"
+
+import (
+ "bytes"
+ "io"
+ "os"
+ "strings"
+)
+
+// Size tries to determine the length of r. If r is an io.Seeker, Size may seek
+// to guess the length.
+func Size(r io.Reader) (size int64, ok bool) {
+ switch rt := r.(type) {
+ case *bytes.Buffer:
+ return int64(rt.Len()), true
+ case *bytes.Reader:
+ return int64(rt.Len()), true
+ case *strings.Reader:
+ return int64(rt.Len()), true
+ case io.Seeker:
+ pos, err := rt.Seek(0, os.SEEK_CUR)
+ if err != nil {
+ return
+ }
+ end, err := rt.Seek(0, os.SEEK_END)
+ if err != nil {
+ return
+ }
+ size = end - pos
+ pos1, err := rt.Seek(pos, os.SEEK_SET)
+ if err != nil || pos1 != pos {
+ msg := "failed to restore seek position"
+ if err != nil {
+ msg += ": " + err.Error()
+ }
+ panic(msg)
+ }
+ return size, true
+ }
+ return 0, false
+}
diff --git a/vendor/go4.org/readerutil/readersize_test.go b/vendor/go4.org/readerutil/readersize_test.go
new file mode 100644
index 0000000..dbe580d
--- /dev/null
+++ b/vendor/go4.org/readerutil/readersize_test.go
@@ -0,0 +1,68 @@
+/*
+Copyright 2012 The Go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package readerutil
+
+import (
+ "bytes"
+ "io"
+ "io/ioutil"
+ "os"
+ "testing"
+)
+
+const text = "HelloWorld"
+
+type testSrc struct {
+ name string
+ src io.Reader
+ want int64
+}
+
+func (tsrc *testSrc) run(t *testing.T) {
+ n, ok := Size(tsrc.src)
+ if !ok {
+ t.Fatalf("failed to read size for %q", tsrc.name)
+ }
+ if n != tsrc.want {
+ t.Fatalf("wanted %v, got %v", tsrc.want, n)
+ }
+}
+
+func TestBytesBuffer(t *testing.T) {
+ buf := bytes.NewBuffer([]byte(text))
+ tsrc := &testSrc{"buffer", buf, int64(len(text))}
+ tsrc.run(t)
+}
+
+func TestSeeker(t *testing.T) {
+ f, err := ioutil.TempFile("", "camliTestReaderSize")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(f.Name())
+ defer f.Close()
+ size, err := f.Write([]byte(text))
+ if err != nil {
+ t.Fatal(err)
+ }
+ pos, err := f.Seek(5, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tsrc := &testSrc{"seeker", f, int64(size) - pos}
+ tsrc.run(t)
+}
diff --git a/vendor/go4.org/readerutil/readerutil.go b/vendor/go4.org/readerutil/readerutil.go
new file mode 100644
index 0000000..61bb2c4
--- /dev/null
+++ b/vendor/go4.org/readerutil/readerutil.go
@@ -0,0 +1,84 @@
+/*
+Copyright 2016 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package readerutil contains io.Reader types.
+package readerutil // import "go4.org/readerutil"
+
+import (
+ "expvar"
+ "io"
+)
+
+// A SizeReaderAt is a ReaderAt with a Size method.
+//
+// An io.SectionReader implements SizeReaderAt.
+type SizeReaderAt interface {
+ Size() int64
+ io.ReaderAt
+}
+
+// A ReadSeekCloser can Read, Seek, and Close.
+type ReadSeekCloser interface {
+ io.Reader
+ io.Seeker
+ io.Closer
+}
+
+type ReaderAtCloser interface {
+ io.ReaderAt
+ io.Closer
+}
+
+// TODO(wathiede): make sure all the stat readers work with code that
+// type asserts ReadFrom/WriteTo.
+
+type varStatReader struct {
+ *expvar.Int
+ r io.Reader
+}
+
+// NewReaderStats returns an io.Reader that will have the number of bytes
+// read from r added to v.
+func NewStatsReader(v *expvar.Int, r io.Reader) io.Reader {
+ return &varStatReader{v, r}
+}
+
+func (v *varStatReader) Read(p []byte) (int, error) {
+ n, err := v.r.Read(p)
+ v.Int.Add(int64(n))
+ return n, err
+}
+
+type varStatReadSeeker struct {
+ *expvar.Int
+ rs io.ReadSeeker
+}
+
+// NewReaderStats returns an io.ReadSeeker that will have the number of bytes
+// read from rs added to v.
+func NewStatsReadSeeker(v *expvar.Int, rs io.ReadSeeker) io.ReadSeeker {
+ return &varStatReadSeeker{v, rs}
+}
+
+func (v *varStatReadSeeker) Read(p []byte) (int, error) {
+ n, err := v.rs.Read(p)
+ v.Int.Add(int64(n))
+ return n, err
+}
+
+func (v *varStatReadSeeker) Seek(offset int64, whence int) (int64, error) {
+ return v.rs.Seek(offset, whence)
+}
diff --git a/vendor/go4.org/readerutil/readerutil_test.go b/vendor/go4.org/readerutil/readerutil_test.go
new file mode 100644
index 0000000..991e03c
--- /dev/null
+++ b/vendor/go4.org/readerutil/readerutil_test.go
@@ -0,0 +1,38 @@
+/*
+Copyright 2016 The Go4 Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package readerutil
+
+import (
+ "expvar"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "strings"
+)
+
+func ExampleNewStatsReader() {
+ var (
+ // r is the io.Reader we'd like to count read from.
+ r = strings.NewReader("Hello world")
+ v = expvar.NewInt("read-bytes")
+ sw = NewStatsReader(v, r)
+ )
+ // Read from the wrapped io.Reader, StatReader will count the bytes.
+ io.Copy(ioutil.Discard, sw)
+ fmt.Printf("Read %s bytes\n", v.String())
+ // Output: Read 11 bytes
+}
diff --git a/vendor/go4.org/readerutil/singlereader/opener.go b/vendor/go4.org/readerutil/singlereader/opener.go
new file mode 100644
index 0000000..f363ae6
--- /dev/null
+++ b/vendor/go4.org/readerutil/singlereader/opener.go
@@ -0,0 +1,118 @@
+/*
+Copyright 2013 The Go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// package singlereader provides Open and Close operations, reusing existing
+// file descriptors when possible.
+package singlereader // import "go4.org/readerutil/singlereader"
+
+import (
+ "sync"
+
+ "go4.org/readerutil"
+ "go4.org/syncutil/singleflight"
+ "go4.org/wkfs"
+)
+
+var (
+ openerGroup singleflight.Group
+
+ openFileMu sync.Mutex // guards openFiles
+ openFiles = make(map[string]*openFile)
+)
+
+type openFile struct {
+ wkfs.File
+ path string // map key of openFiles
+ refCount int
+}
+
+type openFileHandle struct {
+ closed bool
+ *openFile
+}
+
+func (f *openFileHandle) Close() error {
+ openFileMu.Lock()
+ if f.closed {
+ openFileMu.Unlock()
+ return nil
+ }
+ f.closed = true
+ f.refCount--
+ if f.refCount < 0 {
+ panic("unexpected negative refcount")
+ }
+ zero := f.refCount == 0
+ if zero {
+ delete(openFiles, f.path)
+ }
+ openFileMu.Unlock()
+ if !zero {
+ return nil
+ }
+ return f.openFile.File.Close()
+}
+
+// Open opens the given file path for reading, reusing existing file descriptors
+// when possible.
+func Open(path string) (readerutil.ReaderAtCloser, error) {
+ openFileMu.Lock()
+ of := openFiles[path]
+ if of != nil {
+ of.refCount++
+ openFileMu.Unlock()
+ return &openFileHandle{false, of}, nil
+ }
+ openFileMu.Unlock() // release the lock while we call os.Open
+
+ winner := false // this goroutine made it into Do's func
+
+ // Returns an *openFile
+ resi, err := openerGroup.Do(path, func() (interface{}, error) {
+ winner = true
+ f, err := wkfs.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ of := &openFile{
+ File: f,
+ path: path,
+ refCount: 1,
+ }
+ openFileMu.Lock()
+ openFiles[path] = of
+ openFileMu.Unlock()
+ return of, nil
+ })
+ if err != nil {
+ return nil, err
+ }
+ of = resi.(*openFile)
+
+ // If our os.Open was dup-suppressed, we have to increment our
+ // reference count.
+ if !winner {
+ openFileMu.Lock()
+ if of.refCount == 0 {
+ // Winner already closed it. Try again (rare).
+ openFileMu.Unlock()
+ return Open(path)
+ }
+ of.refCount++
+ openFileMu.Unlock()
+ }
+ return &openFileHandle{false, of}, nil
+}
diff --git a/vendor/go4.org/readerutil/singlereader/opener_test.go b/vendor/go4.org/readerutil/singlereader/opener_test.go
new file mode 100644
index 0000000..0df8821
--- /dev/null
+++ b/vendor/go4.org/readerutil/singlereader/opener_test.go
@@ -0,0 +1,77 @@
+/*
+Copyright 2013 The Go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package singlereader
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "runtime"
+ "testing"
+)
+
+func TestOpenSingle(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping in short mode")
+ }
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
+ f, err := ioutil.TempFile("", "foo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(f.Name())
+ contents := []byte("Some file contents")
+ if _, err := f.Write(contents); err != nil {
+ t.Fatal(err)
+ }
+ f.Close()
+
+ const j = 4
+ errc := make(chan error, j)
+ for i := 1; i < j; i++ {
+ go func() {
+ buf := make([]byte, len(contents))
+ for i := 0; i < 400; i++ {
+ rac, err := Open(f.Name())
+ if err != nil {
+ errc <- err
+ return
+ }
+ n, err := rac.ReadAt(buf, 0)
+ if err != nil {
+ errc <- err
+ return
+ }
+ if n != len(contents) || !bytes.Equal(buf, contents) {
+ errc <- fmt.Errorf("read %d, %q; want %d, %q", n, buf, len(contents), contents)
+ return
+ }
+ if err := rac.Close(); err != nil {
+ errc <- err
+ return
+ }
+ }
+ errc <- nil
+ }()
+ }
+ for i := 1; i < j; i++ {
+ if err := <-errc; err != nil {
+ t.Error(err)
+ }
+ }
+}
diff --git a/vendor/go4.org/reflectutil/asm_b.s b/vendor/go4.org/reflectutil/asm_b.s
new file mode 100644
index 0000000..a8e40ac
--- /dev/null
+++ b/vendor/go4.org/reflectutil/asm_b.s
@@ -0,0 +1,17 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.6
+// +build arm
+
+#include "textflag.h"
+#include "funcdata.h"
+
+// func typedmemmove(reflect_rtype, src unsafe.Pointer, size uintptr)
+TEXT ·typedmemmove(SB),(NOSPLIT|WRAPPER),$0-24
+ B runtime·typedmemmove(SB)
+
+// func memmove(dst, src unsafe.Pointer, size uintptr)
+TEXT ·memmove(SB),(NOSPLIT|WRAPPER),$0-24
+ B runtime·memmove(SB)
diff --git a/vendor/go4.org/reflectutil/asm_b_14.s b/vendor/go4.org/reflectutil/asm_b_14.s
new file mode 100644
index 0000000..10f74ff
--- /dev/null
+++ b/vendor/go4.org/reflectutil/asm_b_14.s
@@ -0,0 +1,13 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.5
+// +build arm
+
+#include "textflag.h"
+#include "funcdata.h"
+
+// func memmove(dst, src unsafe.Pointer, size uintptr)
+TEXT ·memmove(SB),(NOSPLIT|WRAPPER),$0-24
+ B runtime·memmove(SB)
diff --git a/vendor/go4.org/reflectutil/asm_jmp.s b/vendor/go4.org/reflectutil/asm_jmp.s
new file mode 100644
index 0000000..862f30e
--- /dev/null
+++ b/vendor/go4.org/reflectutil/asm_jmp.s
@@ -0,0 +1,17 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.5,!js,!safe,!appengine
+// +build amd64 386
+
+#include "textflag.h"
+#include "funcdata.h"
+
+// func typedmemmove(reflect_rtype, src unsafe.Pointer, size uintptr)
+TEXT ·typedmemmove(SB),(NOSPLIT|WRAPPER),$0-24
+ JMP runtime·typedmemmove(SB)
+
+// func memmove(dst, src unsafe.Pointer, size uintptr)
+TEXT ·memmove(SB),(NOSPLIT|WRAPPER),$0-24
+ JMP runtime·memmove(SB)
diff --git a/vendor/go4.org/reflectutil/asm_jmp_14.s b/vendor/go4.org/reflectutil/asm_jmp_14.s
new file mode 100644
index 0000000..95cfdcc
--- /dev/null
+++ b/vendor/go4.org/reflectutil/asm_jmp_14.s
@@ -0,0 +1,13 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.5,!js,!safe,!appengine
+// +build amd64 386
+
+#include "textflag.h"
+#include "funcdata.h"
+
+// func memmove(dst, src unsafe.Pointer, size uintptr)
+TEXT ·memmove(SB),(NOSPLIT|WRAPPER),$0-24
+ JMP runtime·memmove(SB)
diff --git a/vendor/go4.org/reflectutil/reflectutil.go b/vendor/go4.org/reflectutil/reflectutil.go
new file mode 100644
index 0000000..e8f5837
--- /dev/null
+++ b/vendor/go4.org/reflectutil/reflectutil.go
@@ -0,0 +1,40 @@
+// Copyright 2016 The Go 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 reflectutil contains reflect utilities.
+package reflectutil
+
+import "reflect"
+
+// hasPointers reports whether the given type contains any pointers,
+// including any internal pointers in slices, funcs, maps, channels,
+// etc.
+//
+// This function exists for Swapper's internal use, instead of reaching
+// into the runtime's *reflect._rtype kind&kindNoPointers flag.
+func hasPointers(t reflect.Type) bool {
+ if t == nil {
+ panic("nil Type")
+ }
+ k := t.Kind()
+ if k <= reflect.Complex128 {
+ return false
+ }
+ switch k {
+ default:
+ // chan, func, interface, map, ptr, slice, string, unsafepointer
+ // And anything else. It's safer to err on the side of true.
+ return true
+ case reflect.Array:
+ return hasPointers(t.Elem())
+ case reflect.Struct:
+ num := t.NumField()
+ for i := 0; i < num; i++ {
+ if hasPointers(t.Field(i).Type) {
+ return true
+ }
+ }
+ return false
+ }
+}
diff --git a/vendor/go4.org/reflectutil/reflectutil_test.go b/vendor/go4.org/reflectutil/reflectutil_test.go
new file mode 100644
index 0000000..c7a849a
--- /dev/null
+++ b/vendor/go4.org/reflectutil/reflectutil_test.go
@@ -0,0 +1,71 @@
+// Copyright 2016 The Go 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 reflectutil
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestHasPointers(t *testing.T) {
+ tests := []struct {
+ val interface{}
+ want bool
+ }{
+ {false, false},
+ {int(1), false},
+ {int8(1), false},
+ {int16(1), false},
+ {int32(1), false},
+ {int64(1), false},
+ {uint(1), false},
+ {uint8(1), false},
+ {uint16(1), false},
+ {uint32(1), false},
+ {uint64(1), false},
+ {uintptr(1), false},
+ {float32(1.0), false},
+ {float64(1.0), false},
+ {complex64(1.0i), false},
+ {complex128(1.0i), false},
+
+ {[...]int{1, 2}, false},
+ {[...]*int{nil, nil}, true},
+
+ {make(chan bool), true},
+
+ {TestHasPointers, true},
+
+ {map[string]string{"foo": "bar"}, true},
+
+ {new(int), true},
+
+ {[]int{1, 2}, true},
+
+ {"foo", true},
+
+ {struct{}{}, false},
+ {struct{ int }{0}, false},
+ {struct {
+ a int
+ b bool
+ }{0, false}, false},
+ {struct {
+ a int
+ b string
+ }{0, ""}, true},
+ {struct{ *int }{nil}, true},
+ {struct {
+ a *int
+ b int
+ }{nil, 0}, true},
+ }
+ for i, tt := range tests {
+ got := hasPointers(reflect.TypeOf(tt.val))
+ if got != tt.want {
+ t.Errorf("%d. hasPointers(%T) = %v; want %v", i, tt.val, got, tt.want)
+ }
+ }
+}
diff --git a/vendor/go4.org/reflectutil/swapper.go b/vendor/go4.org/reflectutil/swapper.go
new file mode 100644
index 0000000..5d660d3
--- /dev/null
+++ b/vendor/go4.org/reflectutil/swapper.go
@@ -0,0 +1,21 @@
+// Copyright 2016 The Go 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 reflectutil
+
+import "reflect"
+
+// Swapper returns a function which swaps the elements in slice.
+// Swapper panics if the provided interface is not a slice.
+//
+// Its goal is to work safely and efficiently for all versions and
+// variants of Go: pre-Go1.5, Go1.5+, safe, unsafe, App Engine,
+// GopherJS, etc.
+func Swapper(slice interface{}) func(i, j int) {
+ v := reflect.ValueOf(slice)
+ if v.Kind() != reflect.Slice {
+ panic(&reflect.ValueError{Method: "reflectutil.Swapper", Kind: v.Kind()})
+ }
+ return swapper(v)
+}
diff --git a/vendor/go4.org/reflectutil/swapper_safe.go b/vendor/go4.org/reflectutil/swapper_safe.go
new file mode 100644
index 0000000..7c914c6
--- /dev/null
+++ b/vendor/go4.org/reflectutil/swapper_safe.go
@@ -0,0 +1,20 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build js appengine safe ppc64 ppc64le arm64 mips mipsle mips64 mips64le
+
+package reflectutil
+
+import "reflect"
+
+func swapper(slice reflect.Value) func(i, j int) {
+ tmp := reflect.New(slice.Type().Elem()).Elem()
+ return func(i, j int) {
+ v1 := slice.Index(i)
+ v2 := slice.Index(j)
+ tmp.Set(v1)
+ v1.Set(v2)
+ v2.Set(tmp)
+ }
+}
diff --git a/vendor/go4.org/reflectutil/swapper_test.go b/vendor/go4.org/reflectutil/swapper_test.go
new file mode 100644
index 0000000..11897e1
--- /dev/null
+++ b/vendor/go4.org/reflectutil/swapper_test.go
@@ -0,0 +1,110 @@
+// Copyright 2016 The Go 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 reflectutil
+
+import (
+ "fmt"
+ "reflect"
+ "strconv"
+ "testing"
+)
+
+func TestSwapper(t *testing.T) {
+ type I int
+ var a, b, c I
+ type pair struct {
+ x, y int
+ }
+ type pairPtr struct {
+ x, y int
+ p *I
+ }
+ type S string
+
+ tests := []struct {
+ in interface{}
+ i, j int
+ want interface{}
+ }{
+ {
+ in: []int{1, 20, 300},
+ i: 0,
+ j: 2,
+ want: []int{300, 20, 1},
+ },
+ {
+ in: []uintptr{1, 20, 300},
+ i: 0,
+ j: 2,
+ want: []uintptr{300, 20, 1},
+ },
+ {
+ in: []int16{1, 20, 300},
+ i: 0,
+ j: 2,
+ want: []int16{300, 20, 1},
+ },
+ {
+ in: []int8{1, 20, 100},
+ i: 0,
+ j: 2,
+ want: []int8{100, 20, 1},
+ },
+ {
+ in: []*I{&a, &b, &c},
+ i: 0,
+ j: 2,
+ want: []*I{&c, &b, &a},
+ },
+ {
+ in: []string{"eric", "sergey", "larry"},
+ i: 0,
+ j: 2,
+ want: []string{"larry", "sergey", "eric"},
+ },
+ {
+ in: []S{"eric", "sergey", "larry"},
+ i: 0,
+ j: 2,
+ want: []S{"larry", "sergey", "eric"},
+ },
+ {
+ in: []pair{{1, 2}, {3, 4}, {5, 6}},
+ i: 0,
+ j: 2,
+ want: []pair{{5, 6}, {3, 4}, {1, 2}},
+ },
+ {
+ in: []pairPtr{{1, 2, &a}, {3, 4, &b}, {5, 6, &c}},
+ i: 0,
+ j: 2,
+ want: []pairPtr{{5, 6, &c}, {3, 4, &b}, {1, 2, &a}},
+ },
+ }
+ for i, tt := range tests {
+ inStr := fmt.Sprint(tt.in)
+ Swapper(tt.in)(tt.i, tt.j)
+ if !reflect.DeepEqual(tt.in, tt.want) {
+ t.Errorf("%d. swapping %v and %v of %v = %v; want %v", i, tt.i, tt.j, inStr, tt.in, tt.want)
+ }
+ }
+}
+
+func BenchmarkSwap(b *testing.B) {
+ const N = 1024
+ strs := make([]string, N)
+ for i := range strs {
+ strs[i] = strconv.Itoa(i)
+ }
+ swap := Swapper(strs)
+
+ b.ResetTimer()
+ i, j := 0, 1
+ for n := 0; n < b.N; n++ {
+ i = (i + 1) % N
+ j = (j + 2) % N
+ swap(i, j)
+ }
+}
diff --git a/vendor/go4.org/reflectutil/swapper_unsafe.go b/vendor/go4.org/reflectutil/swapper_unsafe.go
new file mode 100644
index 0000000..b40866f
--- /dev/null
+++ b/vendor/go4.org/reflectutil/swapper_unsafe.go
@@ -0,0 +1,106 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !ppc64,!ppc64le,!arm64,!mips,!mipsle,!mips64,!mips64le
+// +build !js,!appengine,!safe
+
+package reflectutil
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+const ptrSize = 4 << (^uintptr(0) >> 63) // unsafe.Sizeof(uintptr(0)) but an ideal const
+
+// arrayAt returns the i-th element of p, a C-array whose elements are
+// eltSize wide (in bytes).
+func arrayAt(p unsafe.Pointer, i int, eltSize uintptr) unsafe.Pointer {
+ return unsafe.Pointer(uintptr(p) + uintptr(i)*eltSize)
+}
+
+type sliceHeader struct {
+ Data unsafe.Pointer
+ Len int
+ Cap int
+}
+
+func swapper(v reflect.Value) func(i, j int) {
+ maxLen := uint(v.Len())
+
+ s := sliceHeader{unsafe.Pointer(v.Pointer()), int(maxLen), int(maxLen)}
+ tt := v.Type()
+ elemt := tt.Elem()
+ typ := unsafe.Pointer(reflect.ValueOf(elemt).Pointer())
+
+ size := elemt.Size()
+ hasPtr := hasPointers(elemt)
+
+ // Some common & small cases, without using memmove:
+ if hasPtr {
+ if size == ptrSize {
+ var ps []unsafe.Pointer
+ *(*sliceHeader)(unsafe.Pointer(&ps)) = s
+ return func(i, j int) { ps[i], ps[j] = ps[j], ps[i] }
+ }
+ if elemt.Kind() == reflect.String {
+ var ss []string
+ *(*sliceHeader)(unsafe.Pointer(&ss)) = s
+ return func(i, j int) { ss[i], ss[j] = ss[j], ss[i] }
+ }
+ } else {
+ switch size {
+ case 8:
+ var is []int64
+ *(*sliceHeader)(unsafe.Pointer(&is)) = s
+ return func(i, j int) { is[i], is[j] = is[j], is[i] }
+ case 4:
+ var is []int32
+ *(*sliceHeader)(unsafe.Pointer(&is)) = s
+ return func(i, j int) { is[i], is[j] = is[j], is[i] }
+ case 2:
+ var is []int16
+ *(*sliceHeader)(unsafe.Pointer(&is)) = s
+ return func(i, j int) { is[i], is[j] = is[j], is[i] }
+ case 1:
+ var is []int8
+ *(*sliceHeader)(unsafe.Pointer(&is)) = s
+ return func(i, j int) { is[i], is[j] = is[j], is[i] }
+ }
+ }
+
+ // Allocate scratch space for swaps:
+ tmpVal := reflect.New(elemt)
+ tmp := unsafe.Pointer(tmpVal.Pointer())
+
+ // If no pointers (or Go 1.4 or below), we don't require typedmemmove:
+ if !haveTypedMemmove || !hasPtr {
+ return func(i, j int) {
+ if uint(i) >= maxLen || uint(j) >= maxLen {
+ panic("reflect: slice index out of range")
+ }
+ val1 := arrayAt(s.Data, i, size)
+ val2 := arrayAt(s.Data, j, size)
+ memmove(tmp, val1, size)
+ memmove(val1, val2, size)
+ memmove(val2, tmp, size)
+ }
+ }
+
+ return func(i, j int) {
+ if uint(i) >= maxLen || uint(j) >= maxLen {
+ panic("reflect: slice index out of range")
+ }
+ val1 := arrayAt(s.Data, i, size)
+ val2 := arrayAt(s.Data, j, size)
+ typedmemmove(typ, tmp, val1)
+ typedmemmove(typ, val1, val2)
+ typedmemmove(typ, val2, tmp)
+ }
+}
+
+// memmove copies size bytes from src to dst.
+// The memory must not contain any pointers.
+//go:noescape
+func memmove(dst, src unsafe.Pointer, size uintptr)
diff --git a/vendor/go4.org/reflectutil/swapper_unsafe_14.go b/vendor/go4.org/reflectutil/swapper_unsafe_14.go
new file mode 100644
index 0000000..7d6f27d
--- /dev/null
+++ b/vendor/go4.org/reflectutil/swapper_unsafe_14.go
@@ -0,0 +1,16 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !ppc64,!ppc64le,!arm64,!mips,!mipsle,!mips64,!mips64le
+// +build !go1.5,!js,!appengine,!safe
+
+package reflectutil
+
+import "unsafe"
+
+const haveTypedMemmove = false
+
+func typedmemmove(reflect_rtype, dst, src unsafe.Pointer) {
+ panic("never called") // only here so swapper_unsafe.go compiles
+}
diff --git a/vendor/go4.org/reflectutil/swapper_unsafe_15.go b/vendor/go4.org/reflectutil/swapper_unsafe_15.go
new file mode 100644
index 0000000..c5fd6ca
--- /dev/null
+++ b/vendor/go4.org/reflectutil/swapper_unsafe_15.go
@@ -0,0 +1,16 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !ppc64,!ppc64le,!arm64,!mips,!mipsle,!mips64,!mips64le
+// +build go1.5,!js,!appengine,!safe
+
+package reflectutil
+
+import "unsafe"
+
+const haveTypedMemmove = true
+
+// typedmemmove copies a value of type t to dst from src.
+//go:noescape
+func typedmemmove(reflect_rtype, dst, src unsafe.Pointer)
diff --git a/vendor/go4.org/sort/example_interface_test.go b/vendor/go4.org/sort/example_interface_test.go
new file mode 100644
index 0000000..31da9e6
--- /dev/null
+++ b/vendor/go4.org/sort/example_interface_test.go
@@ -0,0 +1,45 @@
+// Copyright 2011 The Go 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 sort_test
+
+import (
+ "fmt"
+
+ "go4.org/sort"
+)
+
+type Person struct {
+ Name string
+ Age int
+}
+
+func (p Person) String() string {
+ return fmt.Sprintf("%s: %d", p.Name, p.Age)
+}
+
+// ByAge implements sort.Interface for []Person based on
+// the Age field.
+type ByAge []Person
+
+func (a ByAge) Len() int { return len(a) }
+func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
+
+func ExampleSort() {
+ people := []Person{
+ {"Bob", 31},
+ {"John", 42},
+ {"Michael", 17},
+ {"Jenny", 26},
+ }
+
+ fmt.Println(people)
+ sort.Sort(ByAge(people))
+ fmt.Println(people)
+
+ // Output:
+ // [Bob: 31 John: 42 Michael: 17 Jenny: 26]
+ // [Michael: 17 Jenny: 26 Bob: 31 John: 42]
+}
diff --git a/vendor/go4.org/sort/example_keys_test.go b/vendor/go4.org/sort/example_keys_test.go
new file mode 100644
index 0000000..a8e47e4
--- /dev/null
+++ b/vendor/go4.org/sort/example_keys_test.go
@@ -0,0 +1,96 @@
+// Copyright 2013 The Go 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 sort_test
+
+import (
+ "fmt"
+ "sort"
+)
+
+// A couple of type definitions to make the units clear.
+type earthMass float64
+type au float64
+
+// A Planet defines the properties of a solar system object.
+type Planet struct {
+ name string
+ mass earthMass
+ distance au
+}
+
+// By is the type of a "less" function that defines the ordering of its Planet arguments.
+type By func(p1, p2 *Planet) bool
+
+// Sort is a method on the function type, By, that sorts the argument slice according to the function.
+func (by By) Sort(planets []Planet) {
+ ps := &planetSorter{
+ planets: planets,
+ by: by, // The Sort method's receiver is the function (closure) that defines the sort order.
+ }
+ sort.Sort(ps)
+}
+
+// planetSorter joins a By function and a slice of Planets to be sorted.
+type planetSorter struct {
+ planets []Planet
+ by func(p1, p2 *Planet) bool // Closure used in the Less method.
+}
+
+// Len is part of sort.Interface.
+func (s *planetSorter) Len() int {
+ return len(s.planets)
+}
+
+// Swap is part of sort.Interface.
+func (s *planetSorter) Swap(i, j int) {
+ s.planets[i], s.planets[j] = s.planets[j], s.planets[i]
+}
+
+// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
+func (s *planetSorter) Less(i, j int) bool {
+ return s.by(&s.planets[i], &s.planets[j])
+}
+
+var planets = []Planet{
+ {"Mercury", 0.055, 0.4},
+ {"Venus", 0.815, 0.7},
+ {"Earth", 1.0, 1.0},
+ {"Mars", 0.107, 1.5},
+}
+
+// ExampleSortKeys demonstrates a technique for sorting a struct type using programmable sort criteria.
+func Example_sortKeys() {
+ // Closures that order the Planet structure.
+ name := func(p1, p2 *Planet) bool {
+ return p1.name < p2.name
+ }
+ mass := func(p1, p2 *Planet) bool {
+ return p1.mass < p2.mass
+ }
+ distance := func(p1, p2 *Planet) bool {
+ return p1.distance < p2.distance
+ }
+ decreasingDistance := func(p1, p2 *Planet) bool {
+ return !distance(p1, p2)
+ }
+
+ // Sort the planets by the various criteria.
+ By(name).Sort(planets)
+ fmt.Println("By name:", planets)
+
+ By(mass).Sort(planets)
+ fmt.Println("By mass:", planets)
+
+ By(distance).Sort(planets)
+ fmt.Println("By distance:", planets)
+
+ By(decreasingDistance).Sort(planets)
+ fmt.Println("By decreasing distance:", planets)
+
+ // Output: By name: [{Earth 1 1} {Mars 0.107 1.5} {Mercury 0.055 0.4} {Venus 0.815 0.7}]
+ // By mass: [{Mercury 0.055 0.4} {Mars 0.107 1.5} {Venus 0.815 0.7} {Earth 1 1}]
+ // By distance: [{Mercury 0.055 0.4} {Venus 0.815 0.7} {Earth 1 1} {Mars 0.107 1.5}]
+ // By decreasing distance: [{Mars 0.107 1.5} {Earth 1 1} {Venus 0.815 0.7} {Mercury 0.055 0.4}]
+}
diff --git a/vendor/go4.org/sort/example_multi_test.go b/vendor/go4.org/sort/example_multi_test.go
new file mode 100644
index 0000000..5c99718
--- /dev/null
+++ b/vendor/go4.org/sort/example_multi_test.go
@@ -0,0 +1,133 @@
+// +build go1.6
+
+// Copyright 2013 The Go 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 sort_test
+
+import (
+ "fmt"
+ "sort"
+)
+
+// A Change is a record of source code changes, recording user, language, and delta size.
+type Change struct {
+ user string
+ language string
+ lines int
+}
+
+type lessFunc func(p1, p2 *Change) bool
+
+// multiSorter implements the Sort interface, sorting the changes within.
+type multiSorter struct {
+ changes []Change
+ less []lessFunc
+}
+
+// Sort sorts the argument slice according to the less functions passed to OrderedBy.
+func (ms *multiSorter) Sort(changes []Change) {
+ ms.changes = changes
+ sort.Sort(ms)
+}
+
+// OrderedBy returns a Sorter that sorts using the less functions, in order.
+// Call its Sort method to sort the data.
+func OrderedBy(less ...lessFunc) *multiSorter {
+ return &multiSorter{
+ less: less,
+ }
+}
+
+// Len is part of sort.Interface.
+func (ms *multiSorter) Len() int {
+ return len(ms.changes)
+}
+
+// Swap is part of sort.Interface.
+func (ms *multiSorter) Swap(i, j int) {
+ ms.changes[i], ms.changes[j] = ms.changes[j], ms.changes[i]
+}
+
+// Less is part of sort.Interface. It is implemented by looping along the
+// less functions until it finds a comparison that is either Less or
+// !Less. Note that it can call the less functions twice per call. We
+// could change the functions to return -1, 0, 1 and reduce the
+// number of calls for greater efficiency: an exercise for the reader.
+func (ms *multiSorter) Less(i, j int) bool {
+ p, q := &ms.changes[i], &ms.changes[j]
+ // Try all but the last comparison.
+ var k int
+ for k = 0; k < len(ms.less)-1; k++ {
+ less := ms.less[k]
+ switch {
+ case less(p, q):
+ // p < q, so we have a decision.
+ return true
+ case less(q, p):
+ // p > q, so we have a decision.
+ return false
+ }
+ // p == q; try the next comparison.
+ }
+ // All comparisons to here said "equal", so just return whatever
+ // the final comparison reports.
+ return ms.less[k](p, q)
+}
+
+var changes = []Change{
+ {"gri", "Go", 100},
+ {"ken", "C", 150},
+ {"glenda", "Go", 200},
+ {"rsc", "Go", 200},
+ {"r", "Go", 100},
+ {"ken", "Go", 200},
+ {"dmr", "C", 100},
+ {"r", "C", 150},
+ {"gri", "Smalltalk", 80},
+}
+
+// ExampleMultiKeys demonstrates a technique for sorting a struct type using different
+// sets of multiple fields in the comparison. We chain together "Less" functions, each of
+// which compares a single field.
+func Example_sortMultiKeys() {
+ // Closures that order the Change structure.
+ user := func(c1, c2 *Change) bool {
+ return c1.user < c2.user
+ }
+ language := func(c1, c2 *Change) bool {
+ return c1.language < c2.language
+ }
+ increasingLines := func(c1, c2 *Change) bool {
+ return c1.lines < c2.lines
+ }
+ decreasingLines := func(c1, c2 *Change) bool {
+ return c1.lines > c2.lines // Note: > orders downwards.
+ }
+
+ // Simple use: Sort by user.
+ OrderedBy(user).Sort(changes)
+ fmt.Println("By user:", changes)
+
+ // More examples.
+ OrderedBy(user, increasingLines).Sort(changes)
+ fmt.Println("By user,lines:", changes)
+
+ OrderedBy(language, increasingLines).Sort(changes)
+ fmt.Println("By language,lines: [{dmr C 100} {glenda Go 200} {gri Go 100} {gri Smalltalk 80} {ken Go 200} {ken C 150} {r C 150} {r Go 100} {rsc Go 200}]
+ // By language,= 23 })
+// returns the smallest index i such that data[i] >= 23. If the caller
+// wants to find whether 23 is in the slice, it must test data[i] == 23
+// separately.
+//
+// Searching data sorted in descending order would use the <=
+// operator instead of the >= operator.
+//
+// To complete the example above, the following code tries to find the value
+// x in an integer slice data sorted in ascending order:
+//
+// x := 23
+// i := sort.Search(len(data), func(i int) bool { return data[i] >= x })
+// if i < len(data) && data[i] == x {
+// // x is present at data[i]
+// } else {
+// // x is not present in data,
+// // but i is the index where it would be inserted.
+// }
+//
+// As a more whimsical example, this program guesses your number:
+//
+// func GuessingGame() {
+// var s string
+// fmt.Printf("Pick an integer from 0 to 100.\n")
+// answer := sort.Search(100, func(i int) bool {
+// fmt.Printf("Is your number <= %d? ", i)
+// fmt.Scanf("%s", &s)
+// return s != "" && s[0] == 'y'
+// })
+// fmt.Printf("Your number is %d.\n", answer)
+// }
+//
+func Search(n int, f func(int) bool) int {
+ // Define f(-1) == false and f(n) == true.
+ // Invariant: f(i-1) == false, f(j) == true.
+ i, j := 0, n
+ for i < j {
+ h := i + (j-i)/2 // avoid overflow when computing h
+ // i ≤ h < j
+ if !f(h) {
+ i = h + 1 // preserves f(i-1) == false
+ } else {
+ j = h // preserves f(j) == true
+ }
+ }
+ // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i.
+ return i
+}
+
+// Convenience wrappers for common cases.
+
+// SearchInts searches for x in a sorted slice of ints and returns the index
+// as specified by Search. The return value is the index to insert x if x is
+// not present (it could be len(a)).
+// The slice must be sorted in ascending order.
+//
+func SearchInts(a []int, x int) int {
+ return Search(len(a), func(i int) bool { return a[i] >= x })
+}
+
+// SearchFloat64s searches for x in a sorted slice of float64s and returns the index
+// as specified by Search. The return value is the index to insert x if x is not
+// present (it could be len(a)).
+// The slice must be sorted in ascending order.
+//
+func SearchFloat64s(a []float64, x float64) int {
+ return Search(len(a), func(i int) bool { return a[i] >= x })
+}
+
+// SearchStrings searches for x in a sorted slice of strings and returns the index
+// as specified by Search. The return value is the index to insert x if x is not
+// present (it could be len(a)).
+// The slice must be sorted in ascending order.
+//
+func SearchStrings(a []string, x string) int {
+ return Search(len(a), func(i int) bool { return a[i] >= x })
+}
+
+// Search returns the result of applying SearchInts to the receiver and x.
+func (p IntSlice) Search(x int) int { return SearchInts(p, x) }
+
+// Search returns the result of applying SearchFloat64s to the receiver and x.
+func (p Float64Slice) Search(x float64) int { return SearchFloat64s(p, x) }
+
+// Search returns the result of applying SearchStrings to the receiver and x.
+func (p StringSlice) Search(x string) int { return SearchStrings(p, x) }
diff --git a/vendor/go4.org/sort/search_test.go b/vendor/go4.org/sort/search_test.go
new file mode 100644
index 0000000..ded68eb
--- /dev/null
+++ b/vendor/go4.org/sort/search_test.go
@@ -0,0 +1,161 @@
+// Copyright 2010 The Go 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 sort_test
+
+import (
+ "runtime"
+ . "sort"
+ "testing"
+)
+
+func f(a []int, x int) func(int) bool {
+ return func(i int) bool {
+ return a[i] >= x
+ }
+}
+
+var data = []int{0: -10, 1: -5, 2: 0, 3: 1, 4: 2, 5: 3, 6: 5, 7: 7, 8: 11, 9: 100, 10: 100, 11: 100, 12: 1000, 13: 10000}
+
+var tests = []struct {
+ name string
+ n int
+ f func(int) bool
+ i int
+}{
+ {"empty", 0, nil, 0},
+ {"1 1", 1, func(i int) bool { return i >= 1 }, 1},
+ {"1 true", 1, func(i int) bool { return true }, 0},
+ {"1 false", 1, func(i int) bool { return false }, 1},
+ {"1e9 991", 1e9, func(i int) bool { return i >= 991 }, 991},
+ {"1e9 true", 1e9, func(i int) bool { return true }, 0},
+ {"1e9 false", 1e9, func(i int) bool { return false }, 1e9},
+ {"data -20", len(data), f(data, -20), 0},
+ {"data -10", len(data), f(data, -10), 0},
+ {"data -9", len(data), f(data, -9), 1},
+ {"data -6", len(data), f(data, -6), 1},
+ {"data -5", len(data), f(data, -5), 1},
+ {"data 3", len(data), f(data, 3), 5},
+ {"data 11", len(data), f(data, 11), 8},
+ {"data 99", len(data), f(data, 99), 9},
+ {"data 100", len(data), f(data, 100), 9},
+ {"data 101", len(data), f(data, 101), 12},
+ {"data 10000", len(data), f(data, 10000), 13},
+ {"data 10001", len(data), f(data, 10001), 14},
+ {"descending a", 7, func(i int) bool { return []int{99, 99, 59, 42, 7, 0, -1, -1}[i] <= 7 }, 4},
+ {"descending 7", 1e9, func(i int) bool { return 1e9-i <= 7 }, 1e9 - 7},
+ {"overflow", 2e9, func(i int) bool { return false }, 2e9},
+}
+
+func TestSearch(t *testing.T) {
+ for _, e := range tests {
+ i := Search(e.n, e.f)
+ if i != e.i {
+ t.Errorf("%s: expected index %d; got %d", e.name, e.i, i)
+ }
+ }
+}
+
+// log2 computes the binary logarithm of x, rounded up to the next integer.
+// (log2(0) == 0, log2(1) == 0, log2(2) == 1, log2(3) == 2, etc.)
+//
+func log2(x int) int {
+ n := 0
+ for p := 1; p < x; p += p {
+ // p == 2**n
+ n++
+ }
+ // p/2 < x <= p == 2**n
+ return n
+}
+
+func TestSearchEfficiency(t *testing.T) {
+ n := 100
+ step := 1
+ for exp := 2; exp < 10; exp++ {
+ // n == 10**exp
+ // step == 10**(exp-2)
+ max := log2(n)
+ for x := 0; x < n; x += step {
+ count := 0
+ i := Search(n, func(i int) bool { count++; return i >= x })
+ if i != x {
+ t.Errorf("n = %d: expected index %d; got %d", n, x, i)
+ }
+ if count > max {
+ t.Errorf("n = %d, x = %d: expected <= %d calls; got %d", n, x, max, count)
+ }
+ }
+ n *= 10
+ step *= 10
+ }
+}
+
+// Smoke tests for convenience wrappers - not comprehensive.
+
+var fdata = []float64{0: -3.14, 1: 0, 2: 1, 3: 2, 4: 1000.7}
+var sdata = []string{0: "f", 1: "foo", 2: "foobar", 3: "x"}
+
+var wrappertests = []struct {
+ name string
+ result int
+ i int
+}{
+ {"SearchInts", SearchInts(data, 11), 8},
+ {"SearchFloat64s", SearchFloat64s(fdata, 2.1), 4},
+ {"SearchStrings", SearchStrings(sdata, ""), 0},
+ {"IntSlice.Search", IntSlice(data).Search(0), 2},
+ {"Float64Slice.Search", Float64Slice(fdata).Search(2.0), 3},
+ {"StringSlice.Search", StringSlice(sdata).Search("x"), 3},
+}
+
+func TestSearchWrappers(t *testing.T) {
+ for _, e := range wrappertests {
+ if e.result != e.i {
+ t.Errorf("%s: expected index %d; got %d", e.name, e.i, e.result)
+ }
+ }
+}
+
+func runSearchWrappers() {
+ SearchInts(data, 11)
+ SearchFloat64s(fdata, 2.1)
+ SearchStrings(sdata, "")
+ IntSlice(data).Search(0)
+ Float64Slice(fdata).Search(2.0)
+ StringSlice(sdata).Search("x")
+}
+
+func TestSearchWrappersDontAlloc(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping malloc count in short mode")
+ }
+ if runtime.GOMAXPROCS(0) > 1 {
+ t.Skip("skipping; GOMAXPROCS>1")
+ }
+ allocs := testing.AllocsPerRun(100, runSearchWrappers)
+ if allocs != 0 {
+ t.Errorf("expected no allocs for runSearchWrappers, got %v", allocs)
+ }
+}
+
+func BenchmarkSearchWrappers(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ runSearchWrappers()
+ }
+}
+
+// Abstract exhaustive test: all sizes up to 100,
+// all possible return values. If there are any small
+// corner cases, this test exercises them.
+func TestSearchExhaustive(t *testing.T) {
+ for size := 0; size <= 100; size++ {
+ for targ := 0; targ <= size; targ++ {
+ i := Search(size, func(i int) bool { return i >= targ })
+ if i != targ {
+ t.Errorf("Search(%d, %d) = %d", size, targ, i)
+ }
+ }
+ }
+}
diff --git a/vendor/go4.org/sort/sort.go b/vendor/go4.org/sort/sort.go
new file mode 100644
index 0000000..84e704f
--- /dev/null
+++ b/vendor/go4.org/sort/sort.go
@@ -0,0 +1,623 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:generate go run genzfunc.go
+
+// Package sort provides primitives for sorting slices and user-defined
+// collections.
+//
+// This is a copy of the Go standard library's sort package with the
+// addition of some helpers for sorting slices and using func literals
+// to sort, rather than having to create a sorter type. See the
+// additional MakeInterface, SliceSorter, and Slice functions.
+// Discussion of moving such helpers into the standard library is
+// at:
+//
+// https://golang.org/issue/16721
+//
+// Per Go's "no +1 policy", please only leave a comment on that issue
+// if you have something unique to add. Use Github's emoji reactions
+// otherwise.
+package sort
+
+import (
+ "reflect"
+
+ "go4.org/reflectutil"
+)
+
+// A type, typically a collection, that satisfies sort.Interface can be
+// sorted by the routines in this package. The methods require that the
+// elements of the collection be enumerated by an integer index.
+type Interface interface {
+ // Len is the number of elements in the collection.
+ Len() int
+ // Less reports whether the element with
+ // index i should sort before the element with index j.
+ Less(i, j int) bool
+ // Swap swaps the elements with indexes i and j.
+ Swap(i, j int)
+}
+
+// lessSwap is a pair of Less and Swap function for use with the
+// auto-generated func-optimized variant of sort.go in
+// zfuncversion.go.
+type lessSwap struct {
+ Less func(i, j int) bool
+ Swap func(i, j int)
+}
+
+// MakeInterface returns a sort Interface using the provided length
+// and pair of swap and less functions.
+func MakeInterface(length int, swap func(i, j int), less func(i, j int) bool) Interface {
+ return &funcs{length, lessSwap{less, swap}}
+}
+
+// SliceSorter returns a sort.Interface to sort the provided slice
+// using the provided less function.
+// If the provided interface is not a slice, the function panics.
+func SliceSorter(slice interface{}, less func(i, j int) bool) Interface {
+ return MakeInterface(reflect.ValueOf(slice).Len(), reflectutil.Swapper(slice), less)
+}
+
+// Slice sorts the provided slice using less.
+// If the provided interface is not a slice, the function panics.
+// The sort is not stable. For a stable sort, use sort.Stable with sort.SliceSorter.
+func Slice(slice interface{}, less func(i, j int) bool) {
+ Sort(SliceSorter(slice, less))
+}
+
+// funcs implements Interface, but is recognized by Sort and Stable
+// which use its lessSwap field with the non-interface sorting
+// routines in zfuncversion.go.
+type funcs struct {
+ length int
+ lessSwap
+}
+
+func (f *funcs) Len() int { return f.length }
+func (f *funcs) Swap(i, j int) { f.lessSwap.Swap(i, j) }
+func (f *funcs) Less(i, j int) bool { return f.lessSwap.Less(i, j) }
+
+// Insertion sort
+func insertionSort(data Interface, a, b int) {
+ for i := a + 1; i < b; i++ {
+ for j := i; j > a && data.Less(j, j-1); j-- {
+ data.Swap(j, j-1)
+ }
+ }
+}
+
+// siftDown implements the heap property on data[lo, hi).
+// first is an offset into the array where the root of the heap lies.
+func siftDown(data Interface, lo, hi, first int) {
+ root := lo
+ for {
+ child := 2*root + 1
+ if child >= hi {
+ break
+ }
+ if child+1 < hi && data.Less(first+child, first+child+1) {
+ child++
+ }
+ if !data.Less(first+root, first+child) {
+ return
+ }
+ data.Swap(first+root, first+child)
+ root = child
+ }
+}
+
+func heapSort(data Interface, a, b int) {
+ first := a
+ lo := 0
+ hi := b - a
+
+ // Build heap with greatest element at top.
+ for i := (hi - 1) / 2; i >= 0; i-- {
+ siftDown(data, i, hi, first)
+ }
+
+ // Pop elements, largest first, into end of data.
+ for i := hi - 1; i >= 0; i-- {
+ data.Swap(first, first+i)
+ siftDown(data, lo, i, first)
+ }
+}
+
+// Quicksort, loosely following Bentley and McIlroy,
+// ``Engineering a Sort Function,'' SP&E November 1993.
+
+// medianOfThree moves the median of the three values data[m0], data[m1], data[m2] into data[m1].
+func medianOfThree(data Interface, m1, m0, m2 int) {
+ // sort 3 elements
+ if data.Less(m1, m0) {
+ data.Swap(m1, m0)
+ }
+ // data[m0] <= data[m1]
+ if data.Less(m2, m1) {
+ data.Swap(m2, m1)
+ // data[m0] <= data[m2] && data[m1] < data[m2]
+ if data.Less(m1, m0) {
+ data.Swap(m1, m0)
+ }
+ }
+ // now data[m0] <= data[m1] <= data[m2]
+}
+
+func swapRange(data Interface, a, b, n int) {
+ for i := 0; i < n; i++ {
+ data.Swap(a+i, b+i)
+ }
+}
+
+func doPivot(data Interface, lo, hi int) (midlo, midhi int) {
+ m := lo + (hi-lo)/2 // Written like this to avoid integer overflow.
+ if hi-lo > 40 {
+ // Tukey's ``Ninther,'' median of three medians of three.
+ s := (hi - lo) / 8
+ medianOfThree(data, lo, lo+s, lo+2*s)
+ medianOfThree(data, m, m-s, m+s)
+ medianOfThree(data, hi-1, hi-1-s, hi-1-2*s)
+ }
+ medianOfThree(data, lo, m, hi-1)
+
+ // Invariants are:
+ // data[lo] = pivot (set up by ChoosePivot)
+ // data[lo < i < a] < pivot
+ // data[a <= i < b] <= pivot
+ // data[b <= i < c] unexamined
+ // data[c <= i < hi-1] > pivot
+ // data[hi-1] >= pivot
+ pivot := lo
+ a, c := lo+1, hi-1
+
+ for ; a < c && data.Less(a, pivot); a++ {
+ }
+ b := a
+ for {
+ for ; b < c && !data.Less(pivot, b); b++ { // data[b] <= pivot
+ }
+ for ; b < c && data.Less(pivot, c-1); c-- { // data[c-1] > pivot
+ }
+ if b >= c {
+ break
+ }
+ // data[b] > pivot; data[c-1] <= pivot
+ data.Swap(b, c-1)
+ b++
+ c--
+ }
+ // If hi-c<3 then there are duplicates (by property of median of nine).
+ // Let be a bit more conservative, and set border to 5.
+ protect := hi-c < 5
+ if !protect && hi-c < (hi-lo)/4 {
+ // Lets test some points for equality to pivot
+ dups := 0
+ if !data.Less(pivot, hi-1) { // data[hi-1] = pivot
+ data.Swap(c, hi-1)
+ c++
+ dups++
+ }
+ if !data.Less(b-1, pivot) { // data[b-1] = pivot
+ b--
+ dups++
+ }
+ // m-lo = (hi-lo)/2 > 6
+ // b-lo > (hi-lo)*3/4-1 > 8
+ // ==> m < b ==> data[m] <= pivot
+ if !data.Less(m, pivot) { // data[m] = pivot
+ data.Swap(m, b-1)
+ b--
+ dups++
+ }
+ // if at least 2 points are equal to pivot, assume skewed distribution
+ protect = dups > 1
+ }
+ if protect {
+ // Protect against a lot of duplicates
+ // Add invariant:
+ // data[a <= i < b] unexamined
+ // data[b <= i < c] = pivot
+ for {
+ for ; a < b && !data.Less(b-1, pivot); b-- { // data[b] == pivot
+ }
+ for ; a < b && data.Less(a, pivot); a++ { // data[a] < pivot
+ }
+ if a >= b {
+ break
+ }
+ // data[a] == pivot; data[b-1] < pivot
+ data.Swap(a, b-1)
+ a++
+ b--
+ }
+ }
+ // Swap pivot into middle
+ data.Swap(pivot, b-1)
+ return b - 1, c
+}
+
+func quickSort(data Interface, a, b, maxDepth int) {
+ for b-a > 12 { // Use ShellSort for slices <= 12 elements
+ if maxDepth == 0 {
+ heapSort(data, a, b)
+ return
+ }
+ maxDepth--
+ mlo, mhi := doPivot(data, a, b)
+ // Avoiding recursion on the larger subproblem guarantees
+ // a stack depth of at most lg(b-a).
+ if mlo-a < b-mhi {
+ quickSort(data, a, mlo, maxDepth)
+ a = mhi // i.e., quickSort(data, mhi, b)
+ } else {
+ quickSort(data, mhi, b, maxDepth)
+ b = mlo // i.e., quickSort(data, a, mlo)
+ }
+ }
+ if b-a > 1 {
+ // Do ShellSort pass with gap 6
+ // It could be written in this simplified form cause b-a <= 12
+ for i := a + 6; i < b; i++ {
+ if data.Less(i, i-6) {
+ data.Swap(i, i-6)
+ }
+ }
+ insertionSort(data, a, b)
+ }
+}
+
+// Sort sorts data.
+//
+// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
+// data.Less and data.Swap. The sort is not guaranteed to be stable.
+//
+// To sort slices without creating a type, see Slice.
+func Sort(data Interface) {
+ n := data.Len()
+ if fs, ok := data.(*funcs); ok {
+ quickSort_func(fs.lessSwap, 0, n, maxDepth(n))
+ } else {
+ quickSort(data, 0, n, maxDepth(n))
+ }
+}
+
+// With sorts data given the provided length, swap, and less
+// functions.
+// The sort is not guaranteed to be stable.
+func With(length int, swap func(i, j int), less func(i, j int) bool) {
+ quickSort_func(lessSwap{less, swap}, 0, length, maxDepth(length))
+}
+
+// maxDepth returns a threshold at which quicksort should switch
+// to heapsort. It returns 2*ceil(lg(n+1)).
+func maxDepth(n int) int {
+ var depth int
+ for i := n; i > 0; i >>= 1 {
+ depth++
+ }
+ return depth * 2
+}
+
+type reverse struct {
+ // This embedded Interface permits Reverse to use the methods of
+ // another Interface implementation.
+ Interface
+}
+
+// Less returns the opposite of the embedded implementation's Less method.
+func (r reverse) Less(i, j int) bool {
+ return r.Interface.Less(j, i)
+}
+
+// Reverse returns the reverse order for data.
+func Reverse(data Interface) Interface {
+ return &reverse{data}
+}
+
+// IsSorted reports whether data is sorted.
+func IsSorted(data Interface) bool {
+ n := data.Len()
+ for i := n - 1; i > 0; i-- {
+ if data.Less(i, i-1) {
+ return false
+ }
+ }
+ return true
+}
+
+// Convenience types for common cases
+
+// IntSlice attaches the methods of Interface to []int, sorting in increasing order.
+type IntSlice []int
+
+func (p IntSlice) Len() int { return len(p) }
+func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
+func (p IntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
+// Sort is a convenience method.
+func (p IntSlice) Sort() { Sort(p) }
+
+// Float64Slice attaches the methods of Interface to []float64, sorting in increasing order.
+type Float64Slice []float64
+
+func (p Float64Slice) Len() int { return len(p) }
+func (p Float64Slice) Less(i, j int) bool { return p[i] < p[j] || isNaN(p[i]) && !isNaN(p[j]) }
+func (p Float64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
+// isNaN is a copy of math.IsNaN to avoid a dependency on the math package.
+func isNaN(f float64) bool {
+ return f != f
+}
+
+// Sort is a convenience method.
+func (p Float64Slice) Sort() { Sort(p) }
+
+// StringSlice attaches the methods of Interface to []string, sorting in increasing order.
+type StringSlice []string
+
+func (p StringSlice) Len() int { return len(p) }
+func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
+func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
+// Sort is a convenience method.
+func (p StringSlice) Sort() { Sort(p) }
+
+// Convenience wrappers for common cases
+
+// Ints sorts a slice of ints in increasing order.
+func Ints(a []int) { Sort(IntSlice(a)) }
+
+// Float64s sorts a slice of float64s in increasing order.
+func Float64s(a []float64) { Sort(Float64Slice(a)) }
+
+// Strings sorts a slice of strings in increasing order.
+func Strings(a []string) { Sort(StringSlice(a)) }
+
+// IntsAreSorted tests whether a slice of ints is sorted in increasing order.
+func IntsAreSorted(a []int) bool { return IsSorted(IntSlice(a)) }
+
+// Float64sAreSorted tests whether a slice of float64s is sorted in increasing order.
+func Float64sAreSorted(a []float64) bool { return IsSorted(Float64Slice(a)) }
+
+// StringsAreSorted tests whether a slice of strings is sorted in increasing order.
+func StringsAreSorted(a []string) bool { return IsSorted(StringSlice(a)) }
+
+// Notes on stable sorting:
+// The used algorithms are simple and provable correct on all input and use
+// only logarithmic additional stack space. They perform well if compared
+// experimentally to other stable in-place sorting algorithms.
+//
+// Remarks on other algorithms evaluated:
+// - GCC's 4.6.3 stable_sort with merge_without_buffer from libstdc++:
+// Not faster.
+// - GCC's __rotate for block rotations: Not faster.
+// - "Practical in-place mergesort" from Jyrki Katajainen, Tomi A. Pasanen
+// and Jukka Teuhola; Nordic Journal of Computing 3,1 (1996), 27-40:
+// The given algorithms are in-place, number of Swap and Assignments
+// grow as n log n but the algorithm is not stable.
+// - "Fast Stable In-Place Sorting with O(n) Data Moves" J.I. Munro and
+// V. Raman in Algorithmica (1996) 16, 115-160:
+// This algorithm either needs additional 2n bits or works only if there
+// are enough different elements available to encode some permutations
+// which have to be undone later (so not stable on any input).
+// - All the optimal in-place sorting/merging algorithms I found are either
+// unstable or rely on enough different elements in each step to encode the
+// performed block rearrangements. See also "In-Place Merging Algorithms",
+// Denham Coates-Evely, Department of Computer Science, Kings College,
+// January 2004 and the references in there.
+// - Often "optimal" algorithms are optimal in the number of assignments
+// but Interface has only Swap as operation.
+
+// Stable sorts data while keeping the original order of equal elements.
+//
+// It makes one call to data.Len to determine n, O(n*log(n)) calls to
+// data.Less and O(n*log(n)*log(n)) calls to data.Swap.
+func Stable(data Interface) {
+ if fs, ok := data.(*funcs); ok {
+ stable_func(fs.lessSwap, fs.length)
+ } else {
+ stable(data, data.Len())
+ }
+}
+
+func stable(data Interface, n int) {
+ blockSize := 20 // must be > 0
+ a, b := 0, blockSize
+ for b <= n {
+ insertionSort(data, a, b)
+ a = b
+ b += blockSize
+ }
+ insertionSort(data, a, n)
+
+ for blockSize < n {
+ a, b = 0, 2*blockSize
+ for b <= n {
+ symMerge(data, a, a+blockSize, b)
+ a = b
+ b += 2 * blockSize
+ }
+ if m := a + blockSize; m < n {
+ symMerge(data, a, m, n)
+ }
+ blockSize *= 2
+ }
+}
+
+// SymMerge merges the two sorted subsequences data[a:m] and data[m:b] using
+// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
+// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
+// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
+// Computer Science, pages 714-723. Springer, 2004.
+//
+// Let M = m-a and N = b-n. Wolog M < N.
+// The recursion depth is bound by ceil(log(N+M)).
+// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
+// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
+//
+// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
+// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
+// in the paper carries through for Swap operations, especially as the block
+// swapping rotate uses only O(M+N) Swaps.
+//
+// symMerge assumes non-degenerate arguments: a < m && m < b.
+// Having the caller check this condition eliminates many leaf recursion calls,
+// which improves performance.
+func symMerge(data Interface, a, m, b int) {
+ // Avoid unnecessary recursions of symMerge
+ // by direct insertion of data[a] into data[m:b]
+ // if data[a:m] only contains one element.
+ if m-a == 1 {
+ // Use binary search to find the lowest index i
+ // such that data[i] >= data[a] for m <= i < b.
+ // Exit the search loop with i == b in case no such index exists.
+ i := m
+ j := b
+ for i < j {
+ h := i + (j-i)/2
+ if data.Less(h, a) {
+ i = h + 1
+ } else {
+ j = h
+ }
+ }
+ // Swap values until data[a] reaches the position before i.
+ for k := a; k < i-1; k++ {
+ data.Swap(k, k+1)
+ }
+ return
+ }
+
+ // Avoid unnecessary recursions of symMerge
+ // by direct insertion of data[m] into data[a:m]
+ // if data[m:b] only contains one element.
+ if b-m == 1 {
+ // Use binary search to find the lowest index i
+ // such that data[i] > data[m] for a <= i < m.
+ // Exit the search loop with i == m in case no such index exists.
+ i := a
+ j := m
+ for i < j {
+ h := i + (j-i)/2
+ if !data.Less(m, h) {
+ i = h + 1
+ } else {
+ j = h
+ }
+ }
+ // Swap values until data[m] reaches the position i.
+ for k := m; k > i; k-- {
+ data.Swap(k, k-1)
+ }
+ return
+ }
+
+ mid := a + (b-a)/2
+ n := mid + m
+ var start, r int
+ if m > mid {
+ start = n - b
+ r = mid
+ } else {
+ start = a
+ r = m
+ }
+ p := n - 1
+
+ for start < r {
+ c := start + (r-start)/2
+ if !data.Less(p-c, c) {
+ start = c + 1
+ } else {
+ r = c
+ }
+ }
+
+ end := n - start
+ if start < m && m < end {
+ rotate(data, start, m, end)
+ }
+ if a < start && start < mid {
+ symMerge(data, a, start, mid)
+ }
+ if mid < end && end < b {
+ symMerge(data, mid, end, b)
+ }
+}
+
+// Rotate two consecutives blocks u = data[a:m] and v = data[m:b] in data:
+// Data of the form 'x u v y' is changed to 'x v u y'.
+// Rotate performs at most b-a many calls to data.Swap.
+// Rotate assumes non-degenerate arguments: a < m && m < b.
+func rotate(data Interface, a, m, b int) {
+ i := m - a
+ j := b - m
+
+ for i != j {
+ if i > j {
+ swapRange(data, m-i, m, j)
+ i -= j
+ } else {
+ swapRange(data, m-i, m+j-i, i)
+ j -= i
+ }
+ }
+ // i == j
+ swapRange(data, m-i, m, i)
+}
+
+/*
+Complexity of Stable Sorting
+
+
+Complexity of block swapping rotation
+
+Each Swap puts one new element into its correct, final position.
+Elements which reach their final position are no longer moved.
+Thus block swapping rotation needs |u|+|v| calls to Swaps.
+This is best possible as each element might need a move.
+
+Pay attention when comparing to other optimal algorithms which
+typically count the number of assignments instead of swaps:
+E.g. the optimal algorithm of Dudzinski and Dydek for in-place
+rotations uses O(u + v + gcd(u,v)) assignments which is
+better than our O(3 * (u+v)) as gcd(u,v) <= u.
+
+
+Stable sorting by SymMerge and BlockSwap rotations
+
+SymMerg complexity for same size input M = N:
+Calls to Less: O(M*log(N/M+1)) = O(N*log(2)) = O(N)
+Calls to Swap: O((M+N)*log(M)) = O(2*N*log(N)) = O(N*log(N))
+
+(The following argument does not fuzz over a missing -1 or
+other stuff which does not impact the final result).
+
+Let n = data.Len(). Assume n = 2^k.
+
+Plain merge sort performs log(n) = k iterations.
+On iteration i the algorithm merges 2^(k-i) blocks, each of size 2^i.
+
+Thus iteration i of merge sort performs:
+Calls to Less O(2^(k-i) * 2^i) = O(2^k) = O(2^log(n)) = O(n)
+Calls to Swap O(2^(k-i) * 2^i * log(2^i)) = O(2^k * i) = O(n*i)
+
+In total k = log(n) iterations are performed; so in total:
+Calls to Less O(log(n) * n)
+Calls to Swap O(n + 2*n + 3*n + ... + (k-1)*n + k*n)
+ = O((k/2) * k * n) = O(n * k^2) = O(n * log^2(n))
+
+
+Above results should generalize to arbitrary n = 2^k + p
+and should not be influenced by the initial insertion sort phase:
+Insertion sort is O(n^2) on Swap and Less, thus O(bs^2) per block of
+size bs at n/bs blocks: O(bs*n) Swaps and Less during insertion sort.
+Merge sort iterations start at i = log(bs). With t = log(bs) constant:
+Calls to Less O((log(n)-t) * n + bs*n) = O(log(n)*n + (bs-t)*n)
+ = O(n * log(n))
+Calls to Swap O(n * log^2(n) - (t^2+t)/2*n) = O(n * log^2(n))
+
+*/
diff --git a/vendor/go4.org/sort/sort_test.go b/vendor/go4.org/sort/sort_test.go
new file mode 100644
index 0000000..2df3be8
--- /dev/null
+++ b/vendor/go4.org/sort/sort_test.go
@@ -0,0 +1,695 @@
+// Copyright 2009 The Go 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 sort_test
+
+import (
+ "fmt"
+ "math"
+ "math/rand"
+ "reflect"
+ "strconv"
+ "testing"
+
+ "go4.org/reflectutil"
+ . "go4.org/sort"
+)
+
+var ints = [...]int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
+var float64s = [...]float64{74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3, math.NaN(), math.NaN(), math.Inf(-1), 9845.768, -959.7485, 905, 7.8, 7.8}
+var strings = [...]string{"", "Hello", "foo", "bar", "foo", "f00", "%*&^*&^&", "***"}
+
+func TestSlice(t *testing.T) {
+ s := []int{5, 4, 3, 2, 1}
+ want := []int{1, 2, 3, 4, 5}
+ Slice(s, func(i, j int) bool { return s[i] < s[j] })
+ if !reflect.DeepEqual(s, want) {
+ t.Errorf("sorted = %v; want %v", s, want)
+ }
+}
+
+func TestSortIntSlice(t *testing.T) {
+ data := ints
+ a := IntSlice(data[0:])
+ Sort(a)
+ if !IsSorted(a) {
+ t.Errorf("sorted %v", ints)
+ t.Errorf(" got %v", data)
+ }
+}
+
+func TestSortFloat64Slice(t *testing.T) {
+ data := float64s
+ a := Float64Slice(data[0:])
+ Sort(a)
+ if !IsSorted(a) {
+ t.Errorf("sorted %v", float64s)
+ t.Errorf(" got %v", data)
+ }
+}
+
+func TestSortStringSlice(t *testing.T) {
+ data := strings
+ a := StringSlice(data[0:])
+ Sort(a)
+ if !IsSorted(a) {
+ t.Errorf("sorted %v", strings)
+ t.Errorf(" got %v", data)
+ }
+}
+
+func TestInts(t *testing.T) {
+ data := ints
+ Ints(data[0:])
+ if !IntsAreSorted(data[0:]) {
+ t.Errorf("sorted %v", ints)
+ t.Errorf(" got %v", data)
+ }
+}
+
+func TestFloat64s(t *testing.T) {
+ data := float64s
+ Float64s(data[0:])
+ if !Float64sAreSorted(data[0:]) {
+ t.Errorf("sorted %v", float64s)
+ t.Errorf(" got %v", data)
+ }
+}
+
+func TestStrings(t *testing.T) {
+ data := strings
+ Strings(data[0:])
+ if !StringsAreSorted(data[0:]) {
+ t.Errorf("sorted %v", strings)
+ t.Errorf(" got %v", data)
+ }
+}
+
+func TestStringsWithSwapper(t *testing.T) {
+ data := strings
+ With(len(data), reflectutil.Swapper(data[:]), func(i, j int) bool {
+ return data[i] < data[j]
+ })
+ if !StringsAreSorted(data[:]) {
+ t.Errorf("sorted %v", strings)
+ t.Errorf(" got %v", data)
+ }
+}
+
+func TestSortLarge_Random(t *testing.T) {
+ n := 1000000
+ if testing.Short() {
+ n /= 100
+ }
+ data := make([]int, n)
+ for i := 0; i < len(data); i++ {
+ data[i] = rand.Intn(100)
+ }
+ if IntsAreSorted(data) {
+ t.Fatalf("terrible rand.rand")
+ }
+ Ints(data)
+ if !IntsAreSorted(data) {
+ t.Errorf("sort didn't sort - 1M ints")
+ }
+}
+
+func TestReverseSortIntSlice(t *testing.T) {
+ data := ints
+ data1 := ints
+ a := IntSlice(data[0:])
+ Sort(a)
+ r := IntSlice(data1[0:])
+ Sort(Reverse(r))
+ for i := 0; i < len(data); i++ {
+ if a[i] != r[len(data)-1-i] {
+ t.Errorf("reverse sort didn't sort")
+ }
+ if i > len(data)/2 {
+ break
+ }
+ }
+}
+
+type nonDeterministicTestingData struct {
+ r *rand.Rand
+}
+
+func (t *nonDeterministicTestingData) Len() int {
+ return 500
+}
+func (t *nonDeterministicTestingData) Less(i, j int) bool {
+ if i < 0 || j < 0 || i >= t.Len() || j >= t.Len() {
+ panic("nondeterministic comparison out of bounds")
+ }
+ return t.r.Float32() < 0.5
+}
+func (t *nonDeterministicTestingData) Swap(i, j int) {
+ if i < 0 || j < 0 || i >= t.Len() || j >= t.Len() {
+ panic("nondeterministic comparison out of bounds")
+ }
+}
+
+func TestNonDeterministicComparison(t *testing.T) {
+ // Ensure that sort.Sort does not panic when Less returns inconsistent results.
+ // See https://golang.org/issue/14377.
+ defer func() {
+ if r := recover(); r != nil {
+ t.Error(r)
+ }
+ }()
+
+ td := &nonDeterministicTestingData{
+ r: rand.New(rand.NewSource(0)),
+ }
+
+ for i := 0; i < 10; i++ {
+ Sort(td)
+ }
+}
+
+func BenchmarkSortString1K(b *testing.B) {
+ b.StopTimer()
+ unsorted := make([]string, 1<<10)
+ for i := range unsorted {
+ unsorted[i] = strconv.Itoa(i ^ 0x2cc)
+ }
+ data := make([]string, len(unsorted))
+
+ for i := 0; i < b.N; i++ {
+ copy(data, unsorted)
+ b.StartTimer()
+ Strings(data)
+ b.StopTimer()
+ }
+}
+
+func BenchmarkSortString1K_With(b *testing.B) {
+ b.StopTimer()
+ unsorted := make([]string, 1<<10)
+ for i := range unsorted {
+ unsorted[i] = strconv.Itoa(i ^ 0x2cc)
+ }
+ data := make([]string, len(unsorted))
+
+ for i := 0; i < b.N; i++ {
+ copy(data, unsorted)
+ b.StartTimer()
+ With(len(data),
+ func(i, j int) { data[i], data[j] = data[j], data[i] },
+ func(i, j int) bool {
+ return data[i] < data[j]
+ })
+ b.StopTimer()
+ }
+}
+
+func BenchmarkSortString1K_WithSwapper(b *testing.B) {
+ b.StopTimer()
+ unsorted := make([]string, 1<<10)
+ for i := range unsorted {
+ unsorted[i] = strconv.Itoa(i ^ 0x2cc)
+ }
+ data := make([]string, len(unsorted))
+
+ for i := 0; i < b.N; i++ {
+ copy(data, unsorted)
+ b.StartTimer()
+ With(len(data), reflectutil.Swapper(data), func(i, j int) bool {
+ return data[i] < data[j]
+ })
+ b.StopTimer()
+ }
+}
+
+func BenchmarkStableString1K(b *testing.B) {
+ b.StopTimer()
+ unsorted := make([]string, 1<<10)
+ for i := 0; i < len(data); i++ {
+ unsorted[i] = strconv.Itoa(i ^ 0x2cc)
+ }
+ data := make([]string, len(unsorted))
+
+ for i := 0; i < b.N; i++ {
+ copy(data, unsorted)
+ b.StartTimer()
+ Stable(StringSlice(data))
+ b.StopTimer()
+ }
+}
+
+func BenchmarkSortInt1K(b *testing.B) {
+ b.StopTimer()
+ for i := 0; i < b.N; i++ {
+ data := make([]int, 1<<10)
+ for i := 0; i < len(data); i++ {
+ data[i] = i ^ 0x2cc
+ }
+ b.StartTimer()
+ Ints(data)
+ b.StopTimer()
+ }
+}
+
+func BenchmarkStableInt1K(b *testing.B) {
+ b.StopTimer()
+ unsorted := make([]int, 1<<10)
+ for i := range unsorted {
+ unsorted[i] = i ^ 0x2cc
+ }
+ data := make([]int, len(unsorted))
+ for i := 0; i < b.N; i++ {
+ copy(data, unsorted)
+ b.StartTimer()
+ Stable(IntSlice(data))
+ b.StopTimer()
+ }
+}
+
+func BenchmarkStableInt1K_With(b *testing.B) {
+ b.StopTimer()
+ unsorted := make([]int, 1<<10)
+ for i := range unsorted {
+ unsorted[i] = i ^ 0x2cc
+ }
+ data := make([]int, len(unsorted))
+ for i := 0; i < b.N; i++ {
+ copy(data, unsorted)
+ b.StartTimer()
+ Stable(MakeInterface(
+ len(data),
+ func(i, j int) { data[i], data[j] = data[j], data[i] },
+ func(i, j int) bool { return data[i] < data[j] },
+ ))
+ b.StopTimer()
+ }
+}
+
+func BenchmarkStableInt1K_WithSwapper(b *testing.B) {
+ b.StopTimer()
+ unsorted := make([]int, 1<<10)
+ for i := range unsorted {
+ unsorted[i] = i ^ 0x2cc
+ }
+ data := make([]int, len(unsorted))
+ for i := 0; i < b.N; i++ {
+ copy(data, unsorted)
+ b.StartTimer()
+ Stable(MakeInterface(len(data), reflectutil.Swapper(data), func(i, j int) bool {
+ return data[i] < data[j]
+ }))
+ b.StopTimer()
+ }
+}
+
+func BenchmarkSortInt64K(b *testing.B) {
+ b.StopTimer()
+ for i := 0; i < b.N; i++ {
+ data := make([]int, 1<<16)
+ for i := 0; i < len(data); i++ {
+ data[i] = i ^ 0xcccc
+ }
+ b.StartTimer()
+ Ints(data)
+ b.StopTimer()
+ }
+}
+
+func BenchmarkStableInt64K(b *testing.B) {
+ b.StopTimer()
+ for i := 0; i < b.N; i++ {
+ data := make([]int, 1<<16)
+ for i := 0; i < len(data); i++ {
+ data[i] = i ^ 0xcccc
+ }
+ b.StartTimer()
+ Stable(IntSlice(data))
+ b.StopTimer()
+ }
+}
+
+const (
+ _Sawtooth = iota
+ _Rand
+ _Stagger
+ _Plateau
+ _Shuffle
+ _NDist
+)
+
+const (
+ _Copy = iota
+ _Reverse
+ _ReverseFirstHalf
+ _ReverseSecondHalf
+ _Sorted
+ _Dither
+ _NMode
+)
+
+type testingData struct {
+ desc string
+ t *testing.T
+ data []int
+ maxswap int // number of swaps allowed
+ ncmp, nswap int
+}
+
+func (d *testingData) Len() int { return len(d.data) }
+func (d *testingData) Less(i, j int) bool {
+ d.ncmp++
+ return d.data[i] < d.data[j]
+}
+func (d *testingData) Swap(i, j int) {
+ if d.nswap >= d.maxswap {
+ d.t.Errorf("%s: used %d swaps sorting slice of %d", d.desc, d.nswap, len(d.data))
+ d.t.FailNow()
+ }
+ d.nswap++
+ d.data[i], d.data[j] = d.data[j], d.data[i]
+}
+
+func min(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
+
+func lg(n int) int {
+ i := 0
+ for 1<= d.keys[j]
+}
+
+func (d *adversaryTestingData) Swap(i, j int) {
+ d.data[i], d.data[j] = d.data[j], d.data[i]
+}
+
+func TestAdversary(t *testing.T) {
+ const size = 100
+ data := make([]int, size)
+ for i := 0; i < size; i++ {
+ data[i] = i
+ }
+
+ d := &adversaryTestingData{data, make(map[int]int), 0}
+ Sort(d) // This should degenerate to heapsort.
+}
+
+func TestStableInts(t *testing.T) {
+ data := ints
+ Stable(IntSlice(data[0:]))
+ if !IntsAreSorted(data[0:]) {
+ t.Errorf("nsorted %v\n got %v", ints, data)
+ }
+}
+
+type intPairs []struct {
+ a, b int
+}
+
+// IntPairs compare on a only.
+func (d intPairs) Len() int { return len(d) }
+func (d intPairs) Less(i, j int) bool { return d[i].a < d[j].a }
+func (d intPairs) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
+
+// Record initial order in B.
+func (d intPairs) initB() {
+ for i := range d {
+ d[i].b = i
+ }
+}
+
+// InOrder checks if a-equal elements were not reordered.
+func (d intPairs) inOrder() bool {
+ lastA, lastB := -1, 0
+ for i := 0; i < len(d); i++ {
+ if lastA != d[i].a {
+ lastA = d[i].a
+ lastB = d[i].b
+ continue
+ }
+ if d[i].b <= lastB {
+ return false
+ }
+ lastB = d[i].b
+ }
+ return true
+}
+
+func TestStability(t *testing.T) {
+ n, m := 100000, 1000
+ if testing.Short() {
+ n, m = 1000, 100
+ }
+ data := make(intPairs, n)
+
+ // random distribution
+ for i := 0; i < len(data); i++ {
+ data[i].a = rand.Intn(m)
+ }
+ if IsSorted(data) {
+ t.Fatalf("terrible rand.rand")
+ }
+ data.initB()
+ Stable(data)
+ if !IsSorted(data) {
+ t.Errorf("Stable didn't sort %d ints", n)
+ }
+ if !data.inOrder() {
+ t.Errorf("Stable wasn't stable on %d ints", n)
+ }
+
+ // already sorted
+ data.initB()
+ Stable(data)
+ if !IsSorted(data) {
+ t.Errorf("Stable shuffled sorted %d ints (order)", n)
+ }
+ if !data.inOrder() {
+ t.Errorf("Stable shuffled sorted %d ints (stability)", n)
+ }
+
+ // sorted reversed
+ for i := 0; i < len(data); i++ {
+ data[i].a = len(data) - i
+ }
+ data.initB()
+ Stable(data)
+ if !IsSorted(data) {
+ t.Errorf("Stable didn't sort %d ints", n)
+ }
+ if !data.inOrder() {
+ t.Errorf("Stable wasn't stable on %d ints", n)
+ }
+}
+
+var countOpsSizes = []int{1e2, 3e2, 1e3, 3e3, 1e4, 3e4, 1e5, 3e5, 1e6}
+
+func countOps(t *testing.T, algo func(Interface), name string) {
+ sizes := countOpsSizes
+ if testing.Short() {
+ sizes = sizes[:5]
+ }
+ if !testing.Verbose() {
+ t.Skip("Counting skipped as non-verbose mode.")
+ }
+ for _, n := range sizes {
+ td := testingData{
+ desc: name,
+ t: t,
+ data: make([]int, n),
+ maxswap: 1<<31 - 1,
+ }
+ for i := 0; i < n; i++ {
+ td.data[i] = rand.Intn(n / 5)
+ }
+ algo(&td)
+ t.Logf("%s %8d elements: %11d Swap, %10d Less", name, n, td.nswap, td.ncmp)
+ }
+}
+
+func TestCountStableOps(t *testing.T) { countOps(t, Stable, "Stable") }
+func TestCountSortOps(t *testing.T) { countOps(t, Sort, "Sort ") }
+
+func bench(b *testing.B, size int, algo func(Interface), name string) {
+ b.StopTimer()
+ data := make(intPairs, size)
+ x := ^uint32(0)
+ for i := 0; i < b.N; i++ {
+ for n := size - 3; n <= size+3; n++ {
+ for i := 0; i < len(data); i++ {
+ x += x
+ x ^= 1
+ if int32(x) < 0 {
+ x ^= 0x88888eef
+ }
+ data[i].a = int(x % uint32(n/5))
+ }
+ data.initB()
+ b.StartTimer()
+ algo(data)
+ b.StopTimer()
+ if !IsSorted(data) {
+ b.Errorf("%s did not sort %d ints", name, n)
+ }
+ if name == "Stable" && !data.inOrder() {
+ b.Errorf("%s unstable on %d ints", name, n)
+ }
+ }
+ }
+}
+
+func BenchmarkSort1e2(b *testing.B) { bench(b, 1e2, Sort, "Sort") }
+func BenchmarkStable1e2(b *testing.B) { bench(b, 1e2, Stable, "Stable") }
+func BenchmarkSort1e4(b *testing.B) { bench(b, 1e4, Sort, "Sort") }
+func BenchmarkStable1e4(b *testing.B) { bench(b, 1e4, Stable, "Stable") }
+func BenchmarkSort1e6(b *testing.B) { bench(b, 1e6, Sort, "Sort") }
+func BenchmarkStable1e6(b *testing.B) { bench(b, 1e6, Stable, "Stable") }
diff --git a/vendor/go4.org/sort/zfuncversion.go b/vendor/go4.org/sort/zfuncversion.go
new file mode 100644
index 0000000..7abb18a
--- /dev/null
+++ b/vendor/go4.org/sort/zfuncversion.go
@@ -0,0 +1,265 @@
+// DO NOT EDIT; AUTO-GENERATED from sort.go using genzfunc.go
+
+// Copyright 2016 The Go 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 sort
+
+// Auto-generated variant of sort.go:insertionSort
+func insertionSort_func(data lessSwap, a, b int) {
+ for i := a + 1; i < b; i++ {
+ for j := i; j > a && data.Less(j, j-1); j-- {
+ data.Swap(j, j-1)
+ }
+ }
+}
+
+// Auto-generated variant of sort.go:siftDown
+func siftDown_func(data lessSwap, lo, hi, first int) {
+ root := lo
+ for {
+ child := 2*root + 1
+ if child >= hi {
+ break
+ }
+ if child+1 < hi && data.Less(first+child, first+child+1) {
+ child++
+ }
+ if !data.Less(first+root, first+child) {
+ return
+ }
+ data.Swap(first+root, first+child)
+ root = child
+ }
+}
+
+// Auto-generated variant of sort.go:heapSort
+func heapSort_func(data lessSwap, a, b int) {
+ first := a
+ lo := 0
+ hi := b - a
+ for i := (hi - 1) / 2; i >= 0; i-- {
+ siftDown_func(data, i, hi, first)
+ }
+ for i := hi - 1; i >= 0; i-- {
+ data.Swap(first, first+i)
+ siftDown_func(data, lo, i, first)
+ }
+}
+
+// Auto-generated variant of sort.go:medianOfThree
+func medianOfThree_func(data lessSwap, m1, m0, m2 int) {
+ if data.Less(m1, m0) {
+ data.Swap(m1, m0)
+ }
+ if data.Less(m2, m1) {
+ data.Swap(m2, m1)
+ if data.Less(m1, m0) {
+ data.Swap(m1, m0)
+ }
+ }
+}
+
+// Auto-generated variant of sort.go:swapRange
+func swapRange_func(data lessSwap, a, b, n int) {
+ for i := 0; i < n; i++ {
+ data.Swap(a+i, b+i)
+ }
+}
+
+// Auto-generated variant of sort.go:doPivot
+func doPivot_func(data lessSwap, lo, hi int) (midlo, midhi int) {
+ m := lo + (hi-lo)/2
+ if hi-lo > 40 {
+ s := (hi - lo) / 8
+ medianOfThree_func(data, lo, lo+s, lo+2*s)
+ medianOfThree_func(data, m, m-s, m+s)
+ medianOfThree_func(data, hi-1, hi-1-s, hi-1-2*s)
+ }
+ medianOfThree_func(data, lo, m, hi-1)
+ pivot := lo
+ a, c := lo+1, hi-1
+ for ; a < c && data.Less(a, pivot); a++ {
+ }
+ b := a
+ for {
+ for ; b < c && !data.Less(pivot, b); b++ {
+ }
+ for ; b < c && data.Less(pivot, c-1); c-- {
+ }
+ if b >= c {
+ break
+ }
+ data.Swap(b, c-1)
+ b++
+ c--
+ }
+ protect := hi-c < 5
+ if !protect && hi-c < (hi-lo)/4 {
+ dups := 0
+ if !data.Less(pivot, hi-1) {
+ data.Swap(c, hi-1)
+ c++
+ dups++
+ }
+ if !data.Less(b-1, pivot) {
+ b--
+ dups++
+ }
+ if !data.Less(m, pivot) {
+ data.Swap(m, b-1)
+ b--
+ dups++
+ }
+ protect = dups > 1
+ }
+ if protect {
+ for {
+ for ; a < b && !data.Less(b-1, pivot); b-- {
+ }
+ for ; a < b && data.Less(a, pivot); a++ {
+ }
+ if a >= b {
+ break
+ }
+ data.Swap(a, b-1)
+ a++
+ b--
+ }
+ }
+ data.Swap(pivot, b-1)
+ return b - 1, c
+}
+
+// Auto-generated variant of sort.go:quickSort
+func quickSort_func(data lessSwap, a, b, maxDepth int) {
+ for b-a > 12 {
+ if maxDepth == 0 {
+ heapSort_func(data, a, b)
+ return
+ }
+ maxDepth--
+ mlo, mhi := doPivot_func(data, a, b)
+ if mlo-a < b-mhi {
+ quickSort_func(data, a, mlo, maxDepth)
+ a = mhi
+ } else {
+ quickSort_func(data, mhi, b, maxDepth)
+ b = mlo
+ }
+ }
+ if b-a > 1 {
+ for i := a + 6; i < b; i++ {
+ if data.Less(i, i-6) {
+ data.Swap(i, i-6)
+ }
+ }
+ insertionSort_func(data, a, b)
+ }
+}
+
+// Auto-generated variant of sort.go:stable
+func stable_func(data lessSwap, n int) {
+ blockSize := 20
+ a, b := 0, blockSize
+ for b <= n {
+ insertionSort_func(data, a, b)
+ a = b
+ b += blockSize
+ }
+ insertionSort_func(data, a, n)
+ for blockSize < n {
+ a, b = 0, 2*blockSize
+ for b <= n {
+ symMerge_func(data, a, a+blockSize, b)
+ a = b
+ b += 2 * blockSize
+ }
+ if m := a + blockSize; m < n {
+ symMerge_func(data, a, m, n)
+ }
+ blockSize *= 2
+ }
+}
+
+// Auto-generated variant of sort.go:symMerge
+func symMerge_func(data lessSwap, a, m, b int) {
+ if m-a == 1 {
+ i := m
+ j := b
+ for i < j {
+ h := i + (j-i)/2
+ if data.Less(h, a) {
+ i = h + 1
+ } else {
+ j = h
+ }
+ }
+ for k := a; k < i-1; k++ {
+ data.Swap(k, k+1)
+ }
+ return
+ }
+ if b-m == 1 {
+ i := a
+ j := m
+ for i < j {
+ h := i + (j-i)/2
+ if !data.Less(m, h) {
+ i = h + 1
+ } else {
+ j = h
+ }
+ }
+ for k := m; k > i; k-- {
+ data.Swap(k, k-1)
+ }
+ return
+ }
+ mid := a + (b-a)/2
+ n := mid + m
+ var start, r int
+ if m > mid {
+ start = n - b
+ r = mid
+ } else {
+ start = a
+ r = m
+ }
+ p := n - 1
+ for start < r {
+ c := start + (r-start)/2
+ if !data.Less(p-c, c) {
+ start = c + 1
+ } else {
+ r = c
+ }
+ }
+ end := n - start
+ if start < m && m < end {
+ rotate_func(data, start, m, end)
+ }
+ if a < start && start < mid {
+ symMerge_func(data, a, start, mid)
+ }
+ if mid < end && end < b {
+ symMerge_func(data, mid, end, b)
+ }
+}
+
+// Auto-generated variant of sort.go:rotate
+func rotate_func(data lessSwap, a, m, b int) {
+ i := m - a
+ j := b - m
+ for i != j {
+ if i > j {
+ swapRange_func(data, m-i, m, j)
+ i -= j
+ } else {
+ swapRange_func(data, m-i, m+j-i, i)
+ j -= i
+ }
+ }
+ swapRange_func(data, m-i, m, i)
+}
diff --git a/vendor/go4.org/strutil/intern.go b/vendor/go4.org/strutil/intern.go
new file mode 100644
index 0000000..dbe8713
--- /dev/null
+++ b/vendor/go4.org/strutil/intern.go
@@ -0,0 +1,39 @@
+/*
+Copyright 2013 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package strutil
+
+var internStr = map[string]string{}
+
+// RegisterCommonString adds common strings to the interned string
+// table. This should be called during init from the main
+// goroutine, not later at runtime.
+func RegisterCommonString(s ...string) {
+ for _, v := range s {
+ internStr[v] = v
+ }
+}
+
+// StringFromBytes returns string(v), minimizing copies for common values of v
+// as previously registered with RegisterCommonString.
+func StringFromBytes(v []byte) string {
+ // In Go 1.3, this string conversion in the map lookup does not allocate
+ // to make a new string. We depend on Go 1.3, so this is always free:
+ if s, ok := internStr[string(v)]; ok {
+ return s
+ }
+ return string(v)
+}
diff --git a/vendor/go4.org/strutil/strconv.go b/vendor/go4.org/strutil/strconv.go
new file mode 100644
index 0000000..9b7495f
--- /dev/null
+++ b/vendor/go4.org/strutil/strconv.go
@@ -0,0 +1,117 @@
+/*
+Copyright 2013 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package strutil
+
+import (
+ "errors"
+ "strconv"
+)
+
+// ParseUintBytes is like strconv.ParseUint, but using a []byte.
+func ParseUintBytes(s []byte, base int, bitSize int) (n uint64, err error) {
+ var cutoff, maxVal uint64
+
+ if bitSize == 0 {
+ bitSize = int(strconv.IntSize)
+ }
+
+ s0 := s
+ switch {
+ case len(s) < 1:
+ err = strconv.ErrSyntax
+ goto Error
+
+ case 2 <= base && base <= 36:
+ // valid base; nothing to do
+
+ case base == 0:
+ // Look for octal, hex prefix.
+ switch {
+ case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'):
+ base = 16
+ s = s[2:]
+ if len(s) < 1 {
+ err = strconv.ErrSyntax
+ goto Error
+ }
+ case s[0] == '0':
+ base = 8
+ default:
+ base = 10
+ }
+
+ default:
+ err = errors.New("invalid base " + strconv.Itoa(base))
+ goto Error
+ }
+
+ n = 0
+ cutoff = cutoff64(base)
+ maxVal = 1<= base {
+ n = 0
+ err = strconv.ErrSyntax
+ goto Error
+ }
+
+ if n >= cutoff {
+ // n*base overflows
+ n = 1<<64 - 1
+ err = strconv.ErrRange
+ goto Error
+ }
+ n *= uint64(base)
+
+ n1 := n + uint64(v)
+ if n1 < n || n1 > maxVal {
+ // n+v overflows
+ n = 1<<64 - 1
+ err = strconv.ErrRange
+ goto Error
+ }
+ n = n1
+ }
+
+ return n, nil
+
+Error:
+ return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err}
+}
+
+// Return the first number n such that n*base >= 1<<64.
+func cutoff64(base int) uint64 {
+ if base < 2 {
+ return 0
+ }
+ return (1<<64-1)/uint64(base) + 1
+}
diff --git a/vendor/go4.org/strutil/strutil.go b/vendor/go4.org/strutil/strutil.go
new file mode 100644
index 0000000..243dcec
--- /dev/null
+++ b/vendor/go4.org/strutil/strutil.go
@@ -0,0 +1,200 @@
+/*
+Copyright 2013 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package strutil contains string and byte processing functions.
+package strutil // import "go4.org/strutil"
+
+import (
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+// Fork of Go's implementation in pkg/strings/strings.go:
+// Generic split: splits after each instance of sep,
+// including sepSave bytes of sep in the subarrays.
+func genSplit(dst []string, s, sep string, sepSave, n int) []string {
+ if n == 0 {
+ return nil
+ }
+ if sep == "" {
+ panic("sep is empty")
+ }
+ if n < 0 {
+ n = strings.Count(s, sep) + 1
+ }
+ c := sep[0]
+ start := 0
+ na := 0
+ for i := 0; i+len(sep) <= len(s) && na+1 < n; i++ {
+ if s[i] == c && (len(sep) == 1 || s[i:i+len(sep)] == sep) {
+ dst = append(dst, s[start:i+sepSave])
+ na++
+ start = i + len(sep)
+ i += len(sep) - 1
+ }
+ }
+ dst = append(dst, s[start:])
+ return dst
+}
+
+// AppendSplitN is like strings.SplitN but appends to and returns dst.
+// Unlike strings.SplitN, an empty separator is not supported.
+// The count n determines the number of substrings to return:
+// n > 0: at most n substrings; the last substring will be the unsplit remainder.
+// n == 0: the result is nil (zero substrings)
+// n < 0: all substrings
+func AppendSplitN(dst []string, s, sep string, n int) []string {
+ return genSplit(dst, s, sep, 0, n)
+}
+
+// equalFoldRune compares a and b runes whether they fold equally.
+//
+// The code comes from strings.EqualFold, but shortened to only one rune.
+func equalFoldRune(sr, tr rune) bool {
+ if sr == tr {
+ return true
+ }
+ // Make sr < tr to simplify what follows.
+ if tr < sr {
+ sr, tr = tr, sr
+ }
+ // Fast check for ASCII.
+ if tr < utf8.RuneSelf && 'A' <= sr && sr <= 'Z' {
+ // ASCII, and sr is upper case. tr must be lower case.
+ if tr == sr+'a'-'A' {
+ return true
+ }
+ return false
+ }
+
+ // General case. SimpleFold(x) returns the next equivalent rune > x
+ // or wraps around to smaller values.
+ r := unicode.SimpleFold(sr)
+ for r != sr && r < tr {
+ r = unicode.SimpleFold(r)
+ }
+ if r == tr {
+ return true
+ }
+ return false
+}
+
+// HasPrefixFold is like strings.HasPrefix but uses Unicode case-folding.
+func HasPrefixFold(s, prefix string) bool {
+ if prefix == "" {
+ return true
+ }
+ for _, pr := range prefix {
+ if s == "" {
+ return false
+ }
+ // step with s, too
+ sr, size := utf8.DecodeRuneInString(s)
+ if sr == utf8.RuneError {
+ return false
+ }
+ s = s[size:]
+ if !equalFoldRune(sr, pr) {
+ return false
+ }
+ }
+ return true
+}
+
+// HasSuffixFold is like strings.HasPrefix but uses Unicode case-folding.
+func HasSuffixFold(s, suffix string) bool {
+ if suffix == "" {
+ return true
+ }
+ // count the runes and bytes in s, but only till rune count of suffix
+ bo, so := len(s), len(suffix)
+ for bo > 0 && so > 0 {
+ r, size := utf8.DecodeLastRuneInString(s[:bo])
+ if r == utf8.RuneError {
+ return false
+ }
+ bo -= size
+
+ sr, size := utf8.DecodeLastRuneInString(suffix[:so])
+ if sr == utf8.RuneError {
+ return false
+ }
+ so -= size
+
+ if !equalFoldRune(r, sr) {
+ return false
+ }
+ }
+ return so == 0
+}
+
+// ContainsFold is like strings.Contains but uses Unicode case-folding.
+func ContainsFold(s, substr string) bool {
+ if substr == "" {
+ return true
+ }
+ if s == "" {
+ return false
+ }
+ firstRune := rune(substr[0])
+ if firstRune >= utf8.RuneSelf {
+ firstRune, _ = utf8.DecodeRuneInString(substr)
+ }
+ for i, rune := range s {
+ if equalFoldRune(rune, firstRune) && HasPrefixFold(s[i:], substr) {
+ return true
+ }
+ }
+ return false
+}
+
+// IsPlausibleJSON reports whether s likely contains a JSON object, without
+// actually parsing it. It's meant to be a light heuristic.
+func IsPlausibleJSON(s string) bool {
+ return startsWithOpenBrace(s) && endsWithCloseBrace(s)
+}
+
+func isASCIIWhite(b byte) bool { return b == ' ' || b == '\n' || b == '\r' || b == '\t' }
+
+func startsWithOpenBrace(s string) bool {
+ for len(s) > 0 {
+ switch {
+ case s[0] == '{':
+ return true
+ case isASCIIWhite(s[0]):
+ s = s[1:]
+ default:
+ return false
+ }
+ }
+ return false
+}
+
+func endsWithCloseBrace(s string) bool {
+ for len(s) > 0 {
+ last := len(s) - 1
+ switch {
+ case s[last] == '}':
+ return true
+ case isASCIIWhite(s[last]):
+ s = s[:last]
+ default:
+ return false
+ }
+ }
+ return false
+}
diff --git a/vendor/go4.org/strutil/strutil_test.go b/vendor/go4.org/strutil/strutil_test.go
new file mode 100644
index 0000000..fc470ef
--- /dev/null
+++ b/vendor/go4.org/strutil/strutil_test.go
@@ -0,0 +1,230 @@
+/*
+Copyright 2013 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package strutil
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestAppendSplitN(t *testing.T) {
+ var got []string
+ tests := []struct {
+ s, sep string
+ n int
+ }{
+ {"foo", "|", 1},
+ {"foo", "|", -1},
+ {"foo|bar", "|", 1},
+ {"foo|bar", "|", -1},
+ {"foo|bar|", "|", 2},
+ {"foo|bar|", "|", -1},
+ {"foo|bar|baz", "|", 1},
+ {"foo|bar|baz", "|", 2},
+ {"foo|bar|baz", "|", 3},
+ {"foo|bar|baz", "|", -1},
+ }
+ for _, tt := range tests {
+ want := strings.SplitN(tt.s, tt.sep, tt.n)
+ got = AppendSplitN(got[:0], tt.s, tt.sep, tt.n)
+ if !reflect.DeepEqual(want, got) {
+ t.Errorf("AppendSplitN(%q, %q, %d) = %q; want %q",
+ tt.s, tt.sep, tt.n, got, want)
+ }
+ }
+}
+
+func TestStringFromBytes(t *testing.T) {
+ for _, s := range []string{"foo", "permanode", "file", "zzzz"} {
+ got := StringFromBytes([]byte(s))
+ if got != s {
+ t.Errorf("StringFromBytes(%q) didn't round-trip; got %q instead", s, got)
+ }
+ }
+}
+
+func TestHasPrefixFold(t *testing.T) {
+ tests := []struct {
+ s, prefix string
+ result bool
+ }{
+ {"camli", "CAML", true},
+ {"CAMLI", "caml", true},
+ {"cam", "Cam", true},
+ {"camli", "car", false},
+ {"caml", "camli", false},
+ {"Hello, 世界 dasdsa", "HeLlO, 世界", true},
+ {"Hello, 世界", "HeLlO, 世界-", false},
+
+ {"kelvin", "\u212A" + "elvin", true}, // "\u212A" is the Kelvin temperature sign
+ {"Kelvin", "\u212A" + "elvin", true},
+ {"kelvin", "\u212A" + "el", true},
+ {"Kelvin", "\u212A" + "el", true},
+ {"\u212A" + "elvin", "Kelvin", true},
+ {"\u212A" + "elvin", "kelvin", true},
+ {"\u212A" + "elvin", "Kel", true},
+ {"\u212A" + "elvin", "kel", true},
+ }
+ for _, tt := range tests {
+ r := HasPrefixFold(tt.s, tt.prefix)
+ if r != tt.result {
+ t.Errorf("HasPrefixFold(%q, %q) returned %v", tt.s, tt.prefix, r)
+ }
+ }
+}
+
+func TestHasSuffixFold(t *testing.T) {
+ tests := []struct {
+ s, suffix string
+ result bool
+ }{
+ {"camli", "AMLI", true},
+ {"CAMLI", "amli", true},
+ {"mli", "MLI", true},
+ {"camli", "ali", false},
+ {"amli", "camli", false},
+ {"asas Hello, 世界", "HeLlO, 世界", true},
+ {"Hello, 世界", "HeLlO, 世界-", false},
+ {"KkkkKKkelvin", "\u212A" + "elvin", true}, // "\u212A" is the Kelvin temperature sign
+
+ {"kelvin", "\u212A" + "elvin", true}, // "\u212A" is the Kelvin temperature sign
+ {"Kelvin", "\u212A" + "elvin", true},
+ {"\u212A" + "elvin", "Kelvin", true},
+ {"\u212A" + "elvin", "kelvin", true},
+ {"\u212A" + "elvin", "vin", true},
+ {"\u212A" + "elvin", "viN", true},
+ }
+ for _, tt := range tests {
+ r := HasSuffixFold(tt.s, tt.suffix)
+ if r != tt.result {
+ t.Errorf("HasSuffixFold(%q, %q) returned %v", tt.s, tt.suffix, r)
+ }
+ }
+}
+
+func TestContainsFold(t *testing.T) {
+ // TODO: more tests, more languages.
+ tests := []struct {
+ s, substr string
+ result bool
+ }{
+ {"camli", "CAML", true},
+ {"CAMLI", "caml", true},
+ {"cam", "Cam", true},
+ {"мир", "ми", true},
+ {"МИP", "ми", true},
+ {"КАМЛИЙСТОР", "камлийс", true},
+ {"КаМлИйСтОр", "КаМлИйС", true},
+ {"camli", "car", false},
+ {"caml", "camli", false},
+
+ {"camli", "AMLI", true},
+ {"CAMLI", "amli", true},
+ {"mli", "MLI", true},
+ {"мир", "ир", true},
+ {"МИP", "ми", true},
+ {"КАМЛИЙСТОР", "лийстор", true},
+ {"КаМлИйСтОр", "лИйСтОр", true},
+ {"мир", "р", true},
+ {"camli", "ali", false},
+ {"amli", "camli", false},
+
+ {"МИP", "и", true},
+ {"мир", "и", true},
+ {"КАМЛИЙСТОР", "лийс", true},
+ {"КаМлИйСтОр", "лИйС", true},
+
+ {"árvíztűrő tükörfúrógép", "árvíztŰrŐ", true},
+ {"I love ☕", "i love ☕", true},
+
+ {"k", "\u212A", true}, // "\u212A" is the Kelvin temperature sign
+ {"\u212A" + "elvin", "k", true},
+ {"kelvin", "\u212A" + "elvin", true},
+ {"Kelvin", "\u212A" + "elvin", true},
+ {"\u212A" + "elvin", "Kelvin", true},
+ {"\u212A" + "elvin", "kelvin", true},
+ {"273.15 kelvin", "\u212A" + "elvin", true},
+ {"273.15 Kelvin", "\u212A" + "elvin", true},
+ {"273.15 \u212A" + "elvin", "Kelvin", true},
+ {"273.15 \u212A" + "elvin", "kelvin", true},
+ }
+ for _, tt := range tests {
+ r := ContainsFold(tt.s, tt.substr)
+ if r != tt.result {
+ t.Errorf("ContainsFold(%q, %q) returned %v", tt.s, tt.substr, r)
+ }
+ }
+}
+
+func TestIsPlausibleJSON(t *testing.T) {
+ tests := []struct {
+ in string
+ want bool
+ }{
+ {"{}", true},
+ {" {}", true},
+ {"{} ", true},
+ {"\n\r\t {}\t \r \n", true},
+
+ {"\n\r\t {x\t \r \n", false},
+ {"{x", false},
+ {"x}", false},
+ {"x", false},
+ {"", false},
+ }
+ for _, tt := range tests {
+ got := IsPlausibleJSON(tt.in)
+ if got != tt.want {
+ t.Errorf("IsPlausibleJSON(%q) = %v; want %v", tt.in, got, tt.want)
+ }
+ }
+}
+
+func BenchmarkHasSuffixFoldToLower(tb *testing.B) {
+ a, b := "camlik", "AMLI\u212A"
+ for i := 0; i < tb.N; i++ {
+ if !strings.HasSuffix(strings.ToLower(a), strings.ToLower(b)) {
+ tb.Fatalf("%q should have the same suffix as %q", a, b)
+ }
+ }
+}
+func BenchmarkHasSuffixFold(tb *testing.B) {
+ a, b := "camlik", "AMLI\u212A"
+ for i := 0; i < tb.N; i++ {
+ if !HasSuffixFold(a, b) {
+ tb.Fatalf("%q should have the same suffix as %q", a, b)
+ }
+ }
+}
+
+func BenchmarkHasPrefixFoldToLower(tb *testing.B) {
+ a, b := "kamlistore", "\u212AAMLI"
+ for i := 0; i < tb.N; i++ {
+ if !strings.HasPrefix(strings.ToLower(a), strings.ToLower(b)) {
+ tb.Fatalf("%q should have the same suffix as %q", a, b)
+ }
+ }
+}
+func BenchmarkHasPrefixFold(tb *testing.B) {
+ a, b := "kamlistore", "\u212AAMLI"
+ for i := 0; i < tb.N; i++ {
+ if !HasPrefixFold(a, b) {
+ tb.Fatalf("%q should have the same suffix as %q", a, b)
+ }
+ }
+}
diff --git a/vendor/go4.org/syncutil/gate.go b/vendor/go4.org/syncutil/gate.go
new file mode 100644
index 0000000..e4592be
--- /dev/null
+++ b/vendor/go4.org/syncutil/gate.go
@@ -0,0 +1,41 @@
+/*
+Copyright 2013 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package syncutil
+
+// A Gate limits concurrency.
+type Gate struct {
+ c chan struct{}
+}
+
+// NewGate returns a new gate that will only permit max operations at once.
+func NewGate(max int) *Gate {
+ return &Gate{make(chan struct{}, max)}
+}
+
+// Start starts an operation, blocking until the gate has room.
+func (g *Gate) Start() {
+ g.c <- struct{}{}
+}
+
+// Done finishes an operation.
+func (g *Gate) Done() {
+ select {
+ case <-g.c:
+ default:
+ panic("Done called more than Start")
+ }
+}
diff --git a/vendor/go4.org/syncutil/group.go b/vendor/go4.org/syncutil/group.go
new file mode 100644
index 0000000..dacef4c
--- /dev/null
+++ b/vendor/go4.org/syncutil/group.go
@@ -0,0 +1,64 @@
+/*
+Copyright 2013 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package syncutil
+
+import "sync"
+
+// A Group is like a sync.WaitGroup and coordinates doing
+// multiple things at once. Its zero value is ready to use.
+type Group struct {
+ wg sync.WaitGroup
+ mu sync.Mutex // guards errs
+ errs []error
+}
+
+// Go runs fn in its own goroutine, but does not wait for it to complete.
+// Call Err or Errs to wait for all the goroutines to complete.
+func (g *Group) Go(fn func() error) {
+ g.wg.Add(1)
+ go func() {
+ defer g.wg.Done()
+ err := fn()
+ if err != nil {
+ g.mu.Lock()
+ defer g.mu.Unlock()
+ g.errs = append(g.errs, err)
+ }
+ }()
+}
+
+// Wait waits for all the previous calls to Go to complete.
+func (g *Group) Wait() {
+ g.wg.Wait()
+}
+
+// Err waits for all previous calls to Go to complete and returns the
+// first non-nil error, or nil.
+func (g *Group) Err() error {
+ g.wg.Wait()
+ if len(g.errs) > 0 {
+ return g.errs[0]
+ }
+ return nil
+}
+
+// Errs waits for all previous calls to Go to complete and returns
+// all non-nil errors.
+func (g *Group) Errs() []error {
+ g.wg.Wait()
+ return g.errs
+}
diff --git a/vendor/go4.org/syncutil/once.go b/vendor/go4.org/syncutil/once.go
new file mode 100644
index 0000000..cd276ce
--- /dev/null
+++ b/vendor/go4.org/syncutil/once.go
@@ -0,0 +1,60 @@
+/*
+Copyright 2014 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package syncutil
+
+import (
+ "sync"
+ "sync/atomic"
+)
+
+// A Once will perform a successful action exactly once.
+//
+// Unlike a sync.Once, this Once's func returns an error
+// and is re-armed on failure.
+type Once struct {
+ m sync.Mutex
+ done uint32
+}
+
+// Do calls the function f if and only if Do has not been invoked
+// without error for this instance of Once. In other words, given
+// var once Once
+// if once.Do(f) is called multiple times, only the first call will
+// invoke f, even if f has a different value in each invocation unless
+// f returns an error. A new instance of Once is required for each
+// function to execute.
+//
+// Do is intended for initialization that must be run exactly once. Since f
+// is niladic, it may be necessary to use a function literal to capture the
+// arguments to a function to be invoked by Do:
+// err := config.once.Do(func() error { return config.init(filename) })
+func (o *Once) Do(f func() error) error {
+ if atomic.LoadUint32(&o.done) == 1 {
+ return nil
+ }
+ // Slow-path.
+ o.m.Lock()
+ defer o.m.Unlock()
+ var err error
+ if o.done == 0 {
+ err = f()
+ if err == nil {
+ atomic.StoreUint32(&o.done, 1)
+ }
+ }
+ return err
+}
diff --git a/vendor/go4.org/syncutil/once_test.go b/vendor/go4.org/syncutil/once_test.go
new file mode 100644
index 0000000..e321d50
--- /dev/null
+++ b/vendor/go4.org/syncutil/once_test.go
@@ -0,0 +1,57 @@
+package syncutil
+
+import (
+ "errors"
+ "testing"
+)
+
+func TestOnce(t *testing.T) {
+ timesRan := 0
+ f := func() error {
+ timesRan++
+ return nil
+ }
+
+ once := Once{}
+ grp := Group{}
+
+ for i := 0; i < 10; i++ {
+ grp.Go(func() error { return once.Do(f) })
+ }
+
+ if grp.Err() != nil {
+ t.Errorf("Expected no errors, got %v", grp.Err())
+ }
+
+ if timesRan != 1 {
+ t.Errorf("Expected to run one time, ran %d", timesRan)
+ }
+}
+
+// TestOnceErroring verifies we retry on every error, but stop after
+// the first success.
+func TestOnceErroring(t *testing.T) {
+ timesRan := 0
+ f := func() error {
+ timesRan++
+ if timesRan < 3 {
+ return errors.New("retry")
+ }
+ return nil
+ }
+
+ once := Once{}
+ grp := Group{}
+
+ for i := 0; i < 10; i++ {
+ grp.Go(func() error { return once.Do(f) })
+ }
+
+ if len(grp.Errs()) != 2 {
+ t.Errorf("Expected two errors, got %d", len(grp.Errs()))
+ }
+
+ if timesRan != 3 {
+ t.Errorf("Expected to run two times, ran %d", timesRan)
+ }
+}
diff --git a/vendor/go4.org/syncutil/sem.go b/vendor/go4.org/syncutil/sem.go
new file mode 100644
index 0000000..092655f
--- /dev/null
+++ b/vendor/go4.org/syncutil/sem.go
@@ -0,0 +1,64 @@
+package syncutil
+
+import (
+ "fmt"
+ "log"
+ "sync"
+)
+
+type debugT bool
+
+var debug = debugT(false)
+
+func (d debugT) Printf(format string, args ...interface{}) {
+ if bool(d) {
+ log.Printf(format, args...)
+ }
+}
+
+// Sem implements a semaphore that can have multiple units acquired/released
+// at a time.
+type Sem struct {
+ c *sync.Cond // Protects size
+ max, free int64
+}
+
+// NewSem creates a semaphore with max units available for acquisition.
+func NewSem(max int64) *Sem {
+ return &Sem{
+ c: sync.NewCond(new(sync.Mutex)),
+ free: max,
+ max: max,
+ }
+}
+
+// Acquire will deduct n units from the semaphore. If the deduction would
+// result in the available units falling below zero, the call will block until
+// another go routine returns units via a call to Release. If more units are
+// requested than the semaphore is configured to hold, error will be non-nil.
+func (s *Sem) Acquire(n int64) error {
+ if n > s.max {
+ return fmt.Errorf("sem: attempt to acquire more units than semaphore size %d > %d", n, s.max)
+ }
+ s.c.L.Lock()
+ defer s.c.L.Unlock()
+ for {
+ debug.Printf("Acquire check max %d free %d, n %d", s.max, s.free, n)
+ if s.free >= n {
+ s.free -= n
+ return nil
+ }
+ debug.Printf("Acquire Wait max %d free %d, n %d", s.max, s.free, n)
+ s.c.Wait()
+ }
+}
+
+// Release will return n units to the semaphore and notify any currently
+// blocking Acquire calls.
+func (s *Sem) Release(n int64) {
+ s.c.L.Lock()
+ defer s.c.L.Unlock()
+ debug.Printf("Release max %d free %d, n %d", s.max, s.free, n)
+ s.free += n
+ s.c.Broadcast()
+}
diff --git a/vendor/go4.org/syncutil/sem_test.go b/vendor/go4.org/syncutil/sem_test.go
new file mode 100644
index 0000000..59380e7
--- /dev/null
+++ b/vendor/go4.org/syncutil/sem_test.go
@@ -0,0 +1,33 @@
+package syncutil_test
+
+import (
+ "testing"
+
+ "go4.org/syncutil"
+)
+
+func TestSem(t *testing.T) {
+ s := syncutil.NewSem(5)
+
+ if err := s.Acquire(2); err != nil {
+ t.Fatal(err)
+ }
+ if err := s.Acquire(2); err != nil {
+ t.Fatal(err)
+ }
+
+ go func() {
+ s.Release(2)
+ s.Release(2)
+ }()
+ if err := s.Acquire(5); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestSemErr(t *testing.T) {
+ s := syncutil.NewSem(5)
+ if err := s.Acquire(6); err == nil {
+ t.Fatal("Didn't get expected error for large acquire.")
+ }
+}
diff --git a/vendor/go4.org/syncutil/singleflight/singleflight.go b/vendor/go4.org/syncutil/singleflight/singleflight.go
new file mode 100644
index 0000000..ee2e1b3
--- /dev/null
+++ b/vendor/go4.org/syncutil/singleflight/singleflight.go
@@ -0,0 +1,64 @@
+/*
+Copyright 2013 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package singleflight provides a duplicate function call suppression
+// mechanism.
+package singleflight // import "go4.org/syncutil/singleflight"
+
+import "sync"
+
+// call is an in-flight or completed Do call
+type call struct {
+ wg sync.WaitGroup
+ val interface{}
+ err error
+}
+
+// Group represents a class of work and forms a namespace in which
+// units of work can be executed with duplicate suppression.
+type Group struct {
+ mu sync.Mutex // protects m
+ m map[string]*call // lazily initialized
+}
+
+// Do executes and returns the results of the given function, making
+// sure that only one execution is in-flight for a given key at a
+// time. If a duplicate comes in, the duplicate caller waits for the
+// original to complete and receives the same results.
+func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
+ g.mu.Lock()
+ if g.m == nil {
+ g.m = make(map[string]*call)
+ }
+ if c, ok := g.m[key]; ok {
+ g.mu.Unlock()
+ c.wg.Wait()
+ return c.val, c.err
+ }
+ c := new(call)
+ c.wg.Add(1)
+ g.m[key] = c
+ g.mu.Unlock()
+
+ c.val, c.err = fn()
+ c.wg.Done()
+
+ g.mu.Lock()
+ delete(g.m, key)
+ g.mu.Unlock()
+
+ return c.val, c.err
+}
diff --git a/vendor/go4.org/syncutil/singleflight/singleflight_test.go b/vendor/go4.org/syncutil/singleflight/singleflight_test.go
new file mode 100644
index 0000000..40edcf3
--- /dev/null
+++ b/vendor/go4.org/syncutil/singleflight/singleflight_test.go
@@ -0,0 +1,85 @@
+/*
+Copyright 2013 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package singleflight
+
+import (
+ "errors"
+ "fmt"
+ "sync"
+ "sync/atomic"
+ "testing"
+ "time"
+)
+
+func TestDo(t *testing.T) {
+ var g Group
+ v, err := g.Do("key", func() (interface{}, error) {
+ return "bar", nil
+ })
+ if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
+ t.Errorf("Do = %v; want %v", got, want)
+ }
+ if err != nil {
+ t.Errorf("Do error = %v", err)
+ }
+}
+
+func TestDoErr(t *testing.T) {
+ var g Group
+ someErr := errors.New("Some error")
+ v, err := g.Do("key", func() (interface{}, error) {
+ return nil, someErr
+ })
+ if err != someErr {
+ t.Errorf("Do error = %v; want someErr %v", err, someErr)
+ }
+ if v != nil {
+ t.Errorf("unexpected non-nil value %#v", v)
+ }
+}
+
+func TestDoDupSuppress(t *testing.T) {
+ var g Group
+ c := make(chan string)
+ var calls int32
+ fn := func() (interface{}, error) {
+ atomic.AddInt32(&calls, 1)
+ return <-c, nil
+ }
+
+ const n = 10
+ var wg sync.WaitGroup
+ for i := 0; i < n; i++ {
+ wg.Add(1)
+ go func() {
+ v, err := g.Do("key", fn)
+ if err != nil {
+ t.Errorf("Do error: %v", err)
+ }
+ if v.(string) != "bar" {
+ t.Errorf("got %q; want %q", v, "bar")
+ }
+ wg.Done()
+ }()
+ }
+ time.Sleep(100 * time.Millisecond) // let goroutines above block
+ c <- "bar"
+ wg.Wait()
+ if got := atomic.LoadInt32(&calls); got != 1 {
+ t.Errorf("number of calls = %d; want 1", got)
+ }
+}
diff --git a/vendor/go4.org/syncutil/syncdebug/syncdebug.go b/vendor/go4.org/syncutil/syncdebug/syncdebug.go
new file mode 100644
index 0000000..0fd5d28
--- /dev/null
+++ b/vendor/go4.org/syncutil/syncdebug/syncdebug.go
@@ -0,0 +1,198 @@
+/*
+Copyright 2013 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package syncdebug contains facilities for debugging synchronization
+// problems.
+package syncdebug // import "go4.org/syncutil/syncdebug"
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+ "runtime"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "go4.org/strutil"
+)
+
+// RWMutexTracker is a sync.RWMutex that tracks who owns the current
+// exclusive lock. It's used for debugging deadlocks.
+type RWMutexTracker struct {
+ mu sync.RWMutex
+
+ // Atomic counters for number waiting and having read and write locks.
+ nwaitr int32
+ nwaitw int32
+ nhaver int32
+ nhavew int32 // should always be 0 or 1
+
+ logOnce sync.Once
+
+ hmu sync.Mutex
+ holder []byte
+ holdr map[int64]bool // goroutines holding read lock
+}
+
+const stackBufSize = 16 << 20
+
+var stackBuf = make(chan []byte, 8)
+
+func getBuf() []byte {
+ select {
+ case b := <-stackBuf:
+ return b[:stackBufSize]
+ default:
+ return make([]byte, stackBufSize)
+ }
+}
+
+func putBuf(b []byte) {
+ select {
+ case stackBuf <- b:
+ default:
+ }
+}
+
+var goroutineSpace = []byte("goroutine ")
+
+// GoroutineID returns the current goroutine's ID.
+// Use of this function is almost always a terrible idea.
+// It is also very slow.
+// GoroutineID is intended only for debugging.
+// In particular, it is used by syncutil.
+func GoroutineID() int64 {
+ b := getBuf()
+ defer putBuf(b)
+ b = b[:runtime.Stack(b, false)]
+ // Parse the 4707 out of "goroutine 4707 ["
+ b = bytes.TrimPrefix(b, goroutineSpace)
+ i := bytes.IndexByte(b, ' ')
+ if i < 0 {
+ panic(fmt.Sprintf("No space found in %q", b))
+ }
+ b = b[:i]
+ n, err := strutil.ParseUintBytes(b, 10, 64)
+ if err != nil {
+ panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err))
+ }
+ return int64(n)
+}
+
+func (m *RWMutexTracker) startLogger() {
+ go func() {
+ var buf bytes.Buffer
+ for {
+ time.Sleep(1 * time.Second)
+ buf.Reset()
+ m.hmu.Lock()
+ for gid := range m.holdr {
+ fmt.Fprintf(&buf, " [%d]", gid)
+ }
+ m.hmu.Unlock()
+ log.Printf("Mutex %p: waitW %d haveW %d waitR %d haveR %d %s",
+ m,
+ atomic.LoadInt32(&m.nwaitw),
+ atomic.LoadInt32(&m.nhavew),
+ atomic.LoadInt32(&m.nwaitr),
+ atomic.LoadInt32(&m.nhaver), buf.Bytes())
+ }
+ }()
+}
+
+func (m *RWMutexTracker) Lock() {
+ m.logOnce.Do(m.startLogger)
+ atomic.AddInt32(&m.nwaitw, 1)
+ m.mu.Lock()
+ atomic.AddInt32(&m.nwaitw, -1)
+ atomic.AddInt32(&m.nhavew, 1)
+
+ m.hmu.Lock()
+ defer m.hmu.Unlock()
+ if len(m.holder) == 0 {
+ m.holder = make([]byte, stackBufSize)
+ }
+ m.holder = m.holder[:runtime.Stack(m.holder[:stackBufSize], false)]
+ log.Printf("Lock at %s", string(m.holder))
+}
+
+func (m *RWMutexTracker) Unlock() {
+ m.hmu.Lock()
+ m.holder = nil
+ m.hmu.Unlock()
+
+ atomic.AddInt32(&m.nhavew, -1)
+ m.mu.Unlock()
+}
+
+func (m *RWMutexTracker) RLock() {
+ m.logOnce.Do(m.startLogger)
+ atomic.AddInt32(&m.nwaitr, 1)
+
+ // Catch read-write-read lock. See if somebody (us? via
+ // another goroutine?) already has a read lock, and then
+ // somebody else is waiting to write, meaning our second read
+ // will deadlock.
+ if atomic.LoadInt32(&m.nhaver) > 0 && atomic.LoadInt32(&m.nwaitw) > 0 {
+ buf := getBuf()
+ buf = buf[:runtime.Stack(buf, false)]
+ log.Printf("Potential R-W-R deadlock at: %s", buf)
+ putBuf(buf)
+ }
+
+ m.mu.RLock()
+ atomic.AddInt32(&m.nwaitr, -1)
+ atomic.AddInt32(&m.nhaver, 1)
+
+ gid := GoroutineID()
+ m.hmu.Lock()
+ defer m.hmu.Unlock()
+ if m.holdr == nil {
+ m.holdr = make(map[int64]bool)
+ }
+ if m.holdr[gid] {
+ buf := getBuf()
+ buf = buf[:runtime.Stack(buf, false)]
+ log.Fatalf("Recursive call to RLock: %s", buf)
+ }
+ m.holdr[gid] = true
+}
+
+func stack() []byte {
+ buf := make([]byte, 1024)
+ return buf[:runtime.Stack(buf, false)]
+}
+
+func (m *RWMutexTracker) RUnlock() {
+ atomic.AddInt32(&m.nhaver, -1)
+
+ gid := GoroutineID()
+ m.hmu.Lock()
+ delete(m.holdr, gid)
+ m.hmu.Unlock()
+
+ m.mu.RUnlock()
+}
+
+// Holder returns the stack trace of the current exclusive lock holder's stack
+// when it acquired the lock (with Lock). It returns the empty string if the lock
+// is not currently held.
+func (m *RWMutexTracker) Holder() string {
+ m.hmu.Lock()
+ defer m.hmu.Unlock()
+ return string(m.holder)
+}
diff --git a/vendor/go4.org/syncutil/syncdebug/syncdebug_test.go b/vendor/go4.org/syncutil/syncdebug/syncdebug_test.go
new file mode 100644
index 0000000..58d86e5
--- /dev/null
+++ b/vendor/go4.org/syncutil/syncdebug/syncdebug_test.go
@@ -0,0 +1,30 @@
+/*
+Copyright 2013 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package syncdebug
+
+import "testing"
+
+func TestGoroutineID(t *testing.T) {
+ c := make(chan int64, 2)
+ c <- GoroutineID()
+ go func() {
+ c <- GoroutineID()
+ }()
+ if a, b := <-c, <-c; a == b {
+ t.Errorf("both goroutine IDs were %d; expected different", a)
+ }
+}
diff --git a/vendor/go4.org/syncutil/syncutil.go b/vendor/go4.org/syncutil/syncutil.go
new file mode 100644
index 0000000..851aebd
--- /dev/null
+++ b/vendor/go4.org/syncutil/syncutil.go
@@ -0,0 +1,18 @@
+/*
+Copyright 2014 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package syncutil provides various synchronization utilities.
+package syncutil // import "go4.org/syncutil"
diff --git a/vendor/go4.org/testing/functest/functest.go b/vendor/go4.org/testing/functest/functest.go
new file mode 100644
index 0000000..d3161ac
--- /dev/null
+++ b/vendor/go4.org/testing/functest/functest.go
@@ -0,0 +1,309 @@
+/*
+Copyright 2016 The go4.org Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package functest contains utilities to ease writing table-driven
+// tests for pure functions and method.
+//
+// Example:
+//
+// func square(v int) int { return v * v }
+//
+// func TestFunc(t *testing.T) {
+// f := functest.New(square)
+// f.Test(t,
+// f.In(0).Want(0),
+// f.In(1).Want(1),
+// f.In(2).Want(4),
+// f.In(3).Want(9),
+// )
+// }
+//
+// It can test whether things panic:
+//
+// f := functest.New(condPanic)
+// f.Test(t,
+// f.In(false, nil),
+// f.In(true, "boom").Check(func(res functest.Result) error {
+// if res.Panic != "boom" {
+// return fmt.Errorf("panic = %v; want boom", res.Panic)
+// }
+// return nil
+// }),
+// f.In(true, nil).Check(func(res functest.Result) error {
+// if res.Panic != nil || res.Paniked {
+// return fmt.Errorf("expected panic with nil value, got: %+v", res)
+// }
+// return nil
+// }),
+// )
+//
+// If a test fails, functest does its best to format a useful error message. You can also
+// name test cases:
+//
+// f := functest.New(square)
+// f.Test(t,
+// f.In(0).Want(0),
+// f.In(1).Want(111),
+// f.In(2).Want(4),
+// f.Case("three").In(3).Want(999),
+// )
+//
+// Which would fail like:
+//
+// --- FAIL: TestSquare (0.00s)
+// functest.go:304: square(1) = 1; want 111
+// functest.go:304: three: square(3) = 9; want 999
+// FAIL
+//
+package functest
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+// Func is a wrapper around a func to test.
+// It must be created with New.
+type Func struct {
+ // Name is the name of the function to use in error messages.
+ // In most cases it is initialized by New, unless the function
+ // being tested is an anonymous function.
+ Name string
+
+ f interface{} // the func
+ fv reflect.Value // of f
+}
+
+var removePunc = strings.NewReplacer("(", "", ")", "", "*", "")
+
+// New wraps a function for testing.
+// The provided value f must be a function or method.
+func New(f interface{}) *Func {
+ fv := reflect.ValueOf(f)
+ if fv.Kind() != reflect.Func {
+ panic("argument to New must be a func")
+ }
+ var name string
+ rf := runtime.FuncForPC(fv.Pointer())
+ if rf != nil {
+ name = rf.Name()
+ if methType := strings.LastIndex(name, ".("); methType != -1 {
+ name = removePunc.Replace(name[methType+2:])
+ } else if lastDot := strings.LastIndex(name, "."); lastDot != -1 {
+ name = name[lastDot+1:]
+ if strings.HasPrefix(name, "func") {
+ // Looks like some anonymous function. Prefer naming it "f".
+ name = "f"
+ }
+ }
+ } else {
+ name = "f"
+ }
+
+ return &Func{
+ f: f,
+ fv: fv,
+ Name: name,
+ }
+}
+
+// Result is the result of a function call, for use with Check.
+type Result struct {
+ // Result is the return value(s) of the function.
+ Result []interface{}
+
+ // Panic is the panic value of the function.
+ Panic interface{}
+
+ // Panicked is whether the function paniced.
+ // It can be used to determine whether a function
+ // called panic(nil).
+ Panicked bool
+}
+
+// Case is a test case to run.
+//
+// Test cases can be either named or unnamed, depending on how they're
+// created. Naming cases is optional; all failures messages aim to
+// have useful output and include the input to the function.
+//
+// Unless the function's arity is zero, all cases should have their input
+// set with In.
+//
+// The case's expected output can be set with Want and/or Check.
+type Case struct {
+ f *Func
+ in []interface{}
+ name string // optional
+ want []interface{} // non-nil if we check args
+ checkRes []func(Result) error
+}
+
+// Case returns a new named case. It should be modified before use.
+func (f *Func) Case(name string) *Case {
+ return &Case{f: f, name: name}
+}
+
+// In returns a new unnamed test case. It will be identified by its arguments
+// only.
+func (f *Func) In(args ...interface{}) *Case {
+ return &Case{f: f, in: args}
+}
+
+// In sets the arguments of c used to call f.
+func (c *Case) In(args ...interface{}) *Case {
+ c.in = args
+ return c
+}
+
+// Want sets the expected result values of the test case.
+// Want modifies and returns c.
+// Callers my use both Want and Check.
+func (c *Case) Want(result ...interface{}) *Case {
+ if c.want != nil {
+ panic("duplicate Want declared on functest.Case")
+ }
+ c.want = result
+ numOut := c.f.fv.Type().NumOut()
+ if len(result) != numOut {
+ // TODO: let caller providing only interesting result values, or
+ // provide matchers.
+ panic(fmt.Sprintf("Want called with %d values; function returns %d values", len(result), numOut))
+ }
+ return c
+}
+
+// Check adds a function to check the result of the case's function
+// call. It is a low-level function when Want is insufficient.
+// For instance, it allows checking whether a function panics.
+// If no checker functions are registered, function panics are considered
+// a test failure.
+//
+// Check modifies and returns c.
+// Callers my use both Want and Check, and may use Check multiple times.
+func (c *Case) Check(checker func(Result) error) *Case {
+ c.checkRes = append(c.checkRes, checker)
+ return c
+}
+
+// Test runs the provided test cases against f.
+// If any test cases fail, t.Errorf is called.
+func (f *Func) Test(t testing.TB, cases ...*Case) {
+ for _, tc := range cases {
+ f.testCase(t, tc)
+ }
+}
+
+func (f *Func) checkCall(in []reflect.Value) (out []reflect.Value, didPanic bool, panicValue interface{}) {
+ defer func() { panicValue = recover() }()
+ didPanic = true
+ out = f.fv.Call(in)
+ didPanic = false
+ return
+}
+
+var nilEmptyInterface = reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem())
+
+func (f *Func) testCase(t testing.TB, c *Case) {
+ // Non-variadic:
+ ft := f.fv.Type()
+ inReg := ft.NumIn()
+ if ft.IsVariadic() {
+ inReg--
+ if len(c.in) < inReg {
+ c.errorf(t, ": input has %d arguments; func requires at least %d", len(c.in), inReg)
+ return
+ }
+ } else if len(c.in) != ft.NumIn() {
+ c.errorf(t, ": input has %d arguments; func takes %d", len(c.in), ft.NumIn())
+ return
+ }
+
+ inv := make([]reflect.Value, len(c.in))
+ for i, v := range c.in {
+ if v == nil {
+ inv[i] = nilEmptyInterface
+ } else {
+ inv[i] = reflect.ValueOf(v)
+ }
+ }
+ got, didPanic, panicValue := f.checkCall(inv)
+
+ var goti []interface{}
+ if !didPanic {
+ goti = make([]interface{}, len(got))
+ for i, rv := range got {
+ goti[i] = rv.Interface()
+ }
+ }
+
+ if c.want != nil {
+ if !reflect.DeepEqual(goti, c.want) {
+ c.errorf(t, " = %v; want %v", formatRes(goti), formatRes(c.want))
+ }
+ }
+ for _, checkRes := range c.checkRes {
+ err := checkRes(Result{
+ Result: goti,
+ Panic: panicValue,
+ Panicked: didPanic,
+ })
+ if err != nil {
+ c.errorf(t, ": %v", err)
+ }
+ }
+ if didPanic && (c.checkRes == nil) {
+ c.errorf(t, ": panicked with %v", panicValue)
+ }
+}
+
+func formatRes(res []interface{}) string {
+ var buf bytes.Buffer
+ if len(res) != 1 {
+ buf.WriteByte('(')
+ }
+ formatValues(&buf, res)
+ if len(res) != 1 {
+ buf.WriteByte(')')
+ }
+ return buf.String()
+}
+
+func formatValues(buf *bytes.Buffer, vals []interface{}) {
+ for i, v := range vals {
+ if i != 0 {
+ buf.WriteString(", ")
+ }
+ fmt.Fprintf(buf, "%#v", v)
+ }
+}
+
+func (c *Case) errorf(t testing.TB, format string, args ...interface{}) {
+ var buf bytes.Buffer
+ if c.name != "" {
+ fmt.Fprintf(&buf, "%s: ", c.name)
+ }
+ buf.WriteString(c.f.Name)
+ buf.WriteString("(")
+ formatValues(&buf, c.in)
+ buf.WriteString(")")
+ fmt.Fprintf(&buf, format, args...)
+ t.Errorf("%s", buf.Bytes())
+}
diff --git a/vendor/go4.org/testing/functest/functest_test.go b/vendor/go4.org/testing/functest/functest_test.go
new file mode 100644
index 0000000..66be644
--- /dev/null
+++ b/vendor/go4.org/testing/functest/functest_test.go
@@ -0,0 +1,137 @@
+package functest
+
+import (
+ "bytes"
+ "fmt"
+ "testing"
+)
+
+// trec is a testing.TB which logs Errorf calls to buf
+type trec struct {
+ testing.TB // crash on unimplemented methods
+ buf bytes.Buffer
+}
+
+func (t *trec) Errorf(format string, args ...interface{}) {
+ t.buf.WriteString("ERR: ")
+ fmt.Fprintf(&t.buf, format, args...)
+ t.buf.WriteByte('\n')
+}
+
+func (t *trec) Logf(format string, args ...interface{}) {
+ t.buf.WriteString("LOG: ")
+ fmt.Fprintf(&t.buf, format, args...)
+ t.buf.WriteByte('\n')
+}
+
+func (t *trec) String() string { return t.buf.String() }
+
+func add(a, b int) int { return a + b }
+
+func TestBasic(t *testing.T) {
+ f := New(add)
+ trec := new(trec)
+ f.Test(trec,
+ f.In(1, 2).Want(3),
+ f.In(5, 6).Want(100),
+ f.Case("also wrong").In(5, 6).Want(101),
+ )
+ want := `ERR: add(5, 6) = 11; want 100
+ERR: also wrong: add(5, 6) = 11; want 101
+`
+ if got := trec.String(); got != want {
+ t.Errorf("Output mismatch.\nGot:\n%v\nWant:\n%v\n", got, want)
+ }
+}
+
+func TestBasic_Strings(t *testing.T) {
+ concat := func(a, b string) string { return a + b }
+ f := New(concat)
+ f.Name = "concat"
+ trec := new(trec)
+ f.Test(trec,
+ f.In("a", "b").Want("ab"),
+ f.In("a", "b\x00").Want("ab"),
+ )
+ want := `ERR: concat("a", "b\x00") = "ab\x00"; want "ab"
+`
+ if got := trec.String(); got != want {
+ t.Errorf("Output mismatch.\nGot:\n%v\nWant:\n%v\n", got, want)
+ }
+}
+
+func TestVariadic(t *testing.T) {
+ sumVar := func(vals ...int) (sum int) {
+ for _, v := range vals {
+ sum += v
+ }
+ return
+ }
+
+ f := New(sumVar)
+ f.Name = "sumVar"
+ trec := new(trec)
+ f.Test(trec,
+ f.In().Want(0),
+ f.In().Want(100),
+ f.In(1).Want(1),
+ f.In(1).Want(100),
+ f.In(1, 2).Want(3),
+ f.In(1, 2, 3).Want(6),
+ f.In(1, 2, 3).Want(100),
+ )
+ want := `ERR: sumVar() = 0; want 100
+ERR: sumVar(1) = 1; want 100
+ERR: sumVar(1, 2, 3) = 6; want 100
+`
+ if got := trec.String(); got != want {
+ t.Errorf("Output mismatch.\nGot:\n%v\nWant:\n%v\n", got, want)
+ }
+}
+
+func condPanic(doPanic bool, panicValue interface{}) {
+ if doPanic {
+ panic(panicValue)
+ }
+}
+
+func TestPanic(t *testing.T) {
+ f := New(condPanic)
+ f.Name = "condPanic"
+ trec := new(trec)
+ f.Test(trec,
+ f.In(false, nil),
+ f.In(true, "boom").Check(func(res Result) error {
+ trec.Logf("Got res: %+v", res)
+ if res.Panic != "boom" {
+ return fmt.Errorf("panic = %v; want boom", res.Panic)
+ }
+ return nil
+ }),
+ f.Case("panic with nil").In(true, nil),
+ )
+ want := `LOG: Got res: {Result:[] Panic:boom Panicked:true}
+ERR: panic with nil: condPanic(true, ): panicked with
+`
+ if got := trec.String(); got != want {
+ t.Errorf("Output mismatch.\nGot:\n%v\nWant:\n%v\n", got, want)
+ }
+}
+
+func TestName_AutoFunc(t *testing.T) {
+ testName(t, New(add), "add")
+}
+
+type SomeType struct{}
+
+func (t *SomeType) SomeMethod(int) int { return 123 }
+
+func TestName_AutoMethod(t *testing.T) {
+ testName(t, New((*SomeType).SomeMethod), "SomeType.SomeMethod")
+}
+
+func testName(t *testing.T, f *Func, want string) {
+ if f.Name != want {
+ t.Errorf("name = %q; want %q", f.Name, want)
+ }
+}
diff --git a/vendor/go4.org/types/types.go b/vendor/go4.org/types/types.go
new file mode 100644
index 0000000..7e474c7
--- /dev/null
+++ b/vendor/go4.org/types/types.go
@@ -0,0 +1,147 @@
+/*
+Copyright 2013 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package types provides various common types.
+package types // import "go4.org/types"
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "strings"
+ "sync"
+ "time"
+)
+
+var null_b = []byte("null")
+
+// NopCloser is an io.Closer that does nothing.
+var NopCloser io.Closer = CloseFunc(func() error { return nil })
+
+// EmptyBody is a ReadCloser that returns EOF on Read and does nothing
+// on Close.
+var EmptyBody io.ReadCloser = ioutil.NopCloser(strings.NewReader(""))
+
+// Time3339 is a time.Time which encodes to and from JSON
+// as an RFC 3339 time in UTC.
+type Time3339 time.Time
+
+var (
+ _ json.Marshaler = Time3339{}
+ _ json.Unmarshaler = (*Time3339)(nil)
+)
+
+func (t Time3339) String() string {
+ return time.Time(t).UTC().Format(time.RFC3339Nano)
+}
+
+func (t Time3339) MarshalJSON() ([]byte, error) {
+ if t.Time().IsZero() {
+ return null_b, nil
+ }
+ return json.Marshal(t.String())
+}
+
+func (t *Time3339) UnmarshalJSON(b []byte) error {
+ if bytes.Equal(b, null_b) {
+ *t = Time3339{}
+ return nil
+ }
+ if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' {
+ return fmt.Errorf("types: failed to unmarshal non-string value %q as an RFC 3339 time", b)
+ }
+ s := string(b[1 : len(b)-1])
+ if s == "" {
+ *t = Time3339{}
+ return nil
+ }
+ tm, err := time.Parse(time.RFC3339Nano, s)
+ if err != nil {
+ if strings.HasPrefix(s, "0000-00-00T00:00:00") {
+ *t = Time3339{}
+ return nil
+ }
+ return err
+ }
+ *t = Time3339(tm)
+ return nil
+}
+
+// ParseTime3339OrZero parses a string in RFC3339 format. If it's invalid,
+// the zero time value is returned instead.
+func ParseTime3339OrZero(v string) Time3339 {
+ t, err := time.Parse(time.RFC3339Nano, v)
+ if err != nil {
+ return Time3339{}
+ }
+ return Time3339(t)
+}
+
+func ParseTime3339OrNil(v string) *Time3339 {
+ t, err := time.Parse(time.RFC3339Nano, v)
+ if err != nil {
+ return nil
+ }
+ tm := Time3339(t)
+ return &tm
+}
+
+// Time returns the time as a time.Time with slightly less stutter
+// than a manual conversion.
+func (t Time3339) Time() time.Time {
+ return time.Time(t)
+}
+
+// IsZero returns whether the time is Go zero or Unix zero.
+func (t *Time3339) IsAnyZero() bool {
+ return t == nil || time.Time(*t).IsZero() || time.Time(*t).Unix() == 0
+}
+
+// ByTime sorts times.
+type ByTime []time.Time
+
+func (s ByTime) Len() int { return len(s) }
+func (s ByTime) Less(i, j int) bool { return s[i].Before(s[j]) }
+func (s ByTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
+// NewOnceCloser returns a Closer wrapping c which only calls Close on c
+// once. Subsequent calls to Close return nil.
+func NewOnceCloser(c io.Closer) io.Closer {
+ return &onceCloser{c: c}
+}
+
+type onceCloser struct {
+ mu sync.Mutex
+ c io.Closer
+}
+
+func (c *onceCloser) Close() error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if c.c == nil {
+ return nil
+ }
+ err := c.c.Close()
+ c.c = nil
+ return err
+}
+
+// CloseFunc implements io.Closer with a function.
+type CloseFunc func() error
+
+func (fn CloseFunc) Close() error { return fn() }
diff --git a/vendor/go4.org/types/types_test.go b/vendor/go4.org/types/types_test.go
new file mode 100644
index 0000000..73571b8
--- /dev/null
+++ b/vendor/go4.org/types/types_test.go
@@ -0,0 +1,103 @@
+/*
+Copyright 2013 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package types
+
+import (
+ "encoding/json"
+ "strings"
+ "testing"
+ "time"
+)
+
+func TestTime3339(t *testing.T) {
+ tm := time.Unix(123, 456)
+ t3 := Time3339(tm)
+ type O struct {
+ SomeTime Time3339 `json:"someTime"`
+ }
+ o := &O{SomeTime: t3}
+ got, err := json.Marshal(o)
+ if err != nil {
+ t.Fatal(err)
+ }
+ goodEnc := "{\"someTime\":\"1970-01-01T00:02:03.000000456Z\"}"
+ if string(got) != goodEnc {
+ t.Errorf("Encoding wrong.\n Got: %q\nWant: %q", got, goodEnc)
+ }
+ ogot := &O{}
+ err = json.Unmarshal([]byte(goodEnc), ogot)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !tm.Equal(ogot.SomeTime.Time()) {
+ t.Errorf("Unmarshal got time %v; want %v", ogot.SomeTime.Time(), tm)
+ }
+}
+
+func TestTime3339_Marshal(t *testing.T) {
+ tests := []struct {
+ in time.Time
+ want string
+ }{
+ {time.Time{}, "null"},
+ {time.Unix(1, 0), `"1970-01-01T00:00:01Z"`},
+ }
+ for i, tt := range tests {
+ got, err := Time3339(tt.in).MarshalJSON()
+ if err != nil {
+ t.Errorf("%d. marshal(%v) got error: %v", i, tt.in, err)
+ continue
+ }
+ if string(got) != tt.want {
+ t.Errorf("%d. marshal(%v) = %q; want %q", i, tt.in, got, tt.want)
+ }
+ }
+}
+
+func TestTime3339_empty(t *testing.T) {
+ tests := []struct {
+ enc string
+ z bool
+ }{
+ {enc: "null", z: true},
+ {enc: `""`, z: true},
+ {enc: "0000-00-00T00:00:00Z", z: true},
+ {enc: "0001-01-01T00:00:00Z", z: true},
+ {enc: "1970-01-01T00:00:00Z", z: true},
+ {enc: "2001-02-03T04:05:06Z", z: false},
+ {enc: "2001-02-03T04:05:06+06:00", z: false},
+ {enc: "2001-02-03T04:05:06-06:00", z: false},
+ {enc: "2001-02-03T04:05:06.123456789Z", z: false},
+ {enc: "2001-02-03T04:05:06.123456789+06:00", z: false},
+ {enc: "2001-02-03T04:05:06.123456789-06:00", z: false},
+ }
+ for _, tt := range tests {
+ var tm Time3339
+ enc := tt.enc
+ if strings.Contains(enc, "T") {
+ enc = "\"" + enc + "\""
+ }
+ err := json.Unmarshal([]byte(enc), &tm)
+ if err != nil {
+ t.Errorf("unmarshal %q = %v", enc, err)
+ }
+ if tm.IsAnyZero() != tt.z {
+ t.Errorf("unmarshal %q = %v (%d), %v; zero=%v; want %v", tt.enc, tm.Time(), tm.Time().Unix(), err,
+ !tt.z, tt.z)
+ }
+ }
+}
diff --git a/vendor/go4.org/wkfs/gcs/gcs.go b/vendor/go4.org/wkfs/gcs/gcs.go
new file mode 100644
index 0000000..7d29e56
--- /dev/null
+++ b/vendor/go4.org/wkfs/gcs/gcs.go
@@ -0,0 +1,199 @@
+/*
+Copyright 2014 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package gcs registers a Google Cloud Storage filesystem at the
+// well-known /gcs/ filesystem path if the current machine is running
+// on Google Compute Engine.
+//
+// It was initially only meant for small files, and as such, it can only
+// read files smaller than 1MB for now.
+package gcs // import "go4.org/wkfs/gcs"
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path"
+ "strings"
+ "time"
+
+ "cloud.google.com/go/compute/metadata"
+ "cloud.google.com/go/storage"
+ "go4.org/wkfs"
+ "golang.org/x/net/context"
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/google"
+ "google.golang.org/api/option"
+)
+
+// Max size for all files read, because we use a bytes.Reader as our file
+// reader, instead of storage.NewReader. This is because we get all wkfs.File
+// methods for free by embedding a bytes.Reader. This filesystem was only supposed
+// to be for configuration data only, so this is ok for now.
+const maxSize = 1 << 20
+
+func init() {
+ if !metadata.OnGCE() {
+ return
+ }
+ hc, err := google.DefaultClient(oauth2.NoContext)
+ if err != nil {
+ registerBrokenFS(fmt.Errorf("could not get http client for context: %v", err))
+ return
+ }
+ ctx := context.Background()
+ sc, err := storage.NewClient(ctx, option.WithHTTPClient(hc))
+ if err != nil {
+ registerBrokenFS(fmt.Errorf("could not get cloud storage client: %v", err))
+ return
+ }
+ wkfs.RegisterFS("/gcs/", &gcsFS{
+ ctx: ctx,
+ sc: sc,
+ })
+}
+
+type gcsFS struct {
+ ctx context.Context
+ sc *storage.Client
+ err error // sticky error
+}
+
+func registerBrokenFS(err error) {
+ wkfs.RegisterFS("/gcs/", &gcsFS{
+ err: err,
+ })
+}
+
+func (fs *gcsFS) parseName(name string) (bucket, fileName string, err error) {
+ if fs.err != nil {
+ return "", "", fs.err
+ }
+ name = strings.TrimPrefix(name, "/gcs/")
+ i := strings.Index(name, "/")
+ if i < 0 {
+ return name, "", nil
+ }
+ return name[:i], name[i+1:], nil
+}
+
+// Open opens the named file for reading. It returns an error if the file size
+// is larger than 1 << 20.
+func (fs *gcsFS) Open(name string) (wkfs.File, error) {
+ bucket, fileName, err := fs.parseName(name)
+ if err != nil {
+ return nil, err
+ }
+ obj := fs.sc.Bucket(bucket).Object(fileName)
+ attrs, err := obj.Attrs(fs.ctx)
+ if err != nil {
+ return nil, err
+ }
+ size := attrs.Size
+ if size > maxSize {
+ return nil, fmt.Errorf("file %s too large (%d bytes) for /gcs/ filesystem", name, size)
+ }
+ rc, err := obj.NewReader(fs.ctx)
+ if err != nil {
+ return nil, err
+ }
+ defer rc.Close()
+
+ slurp, err := ioutil.ReadAll(io.LimitReader(rc, size))
+ if err != nil {
+ return nil, err
+ }
+ return &file{
+ name: name,
+ Reader: bytes.NewReader(slurp),
+ }, nil
+}
+
+func (fs *gcsFS) Stat(name string) (os.FileInfo, error) { return fs.Lstat(name) }
+func (fs *gcsFS) Lstat(name string) (os.FileInfo, error) {
+ bucket, fileName, err := fs.parseName(name)
+ if err != nil {
+ return nil, err
+ }
+ attrs, err := fs.sc.Bucket(bucket).Object(fileName).Attrs(fs.ctx)
+ if err == storage.ErrObjectNotExist {
+ return nil, os.ErrNotExist
+ }
+ if err != nil {
+ return nil, err
+ }
+ return &statInfo{
+ name: attrs.Name,
+ size: attrs.Size,
+ }, nil
+}
+
+func (fs *gcsFS) MkdirAll(path string, perm os.FileMode) error { return nil }
+
+func (fs *gcsFS) OpenFile(name string, flag int, perm os.FileMode) (wkfs.FileWriter, error) {
+ bucket, fileName, err := fs.parseName(name)
+ if err != nil {
+ return nil, err
+ }
+ switch flag {
+ case os.O_WRONLY | os.O_CREATE | os.O_EXCL:
+ case os.O_WRONLY | os.O_CREATE | os.O_TRUNC:
+ default:
+ return nil, fmt.Errorf("Unsupported OpenFlag flag mode %d on Google Cloud Storage", flag)
+ }
+ if flag&os.O_EXCL != 0 {
+ if _, err := fs.Stat(name); err == nil {
+ return nil, os.ErrExist
+ }
+ }
+ // TODO(mpl): consider adding perm to the object's ObjectAttrs.Metadata
+ return fs.sc.Bucket(bucket).Object(fileName).NewWriter(fs.ctx), nil
+}
+
+func (fs *gcsFS) Remove(name string) error {
+ bucket, fileName, err := fs.parseName(name)
+ if err != nil {
+ return err
+ }
+ return fs.sc.Bucket(bucket).Object(fileName).Delete(fs.ctx)
+}
+
+type statInfo struct {
+ name string
+ size int64
+ isDir bool
+ modtime time.Time
+}
+
+func (si *statInfo) IsDir() bool { return si.isDir }
+func (si *statInfo) ModTime() time.Time { return si.modtime }
+func (si *statInfo) Mode() os.FileMode { return 0644 }
+func (si *statInfo) Name() string { return path.Base(si.name) }
+func (si *statInfo) Size() int64 { return si.size }
+func (si *statInfo) Sys() interface{} { return nil }
+
+type file struct {
+ name string
+ *bytes.Reader
+}
+
+func (*file) Close() error { return nil }
+func (f *file) Name() string { return path.Base(f.name) }
+func (f *file) Stat() (os.FileInfo, error) {
+ panic("Stat not implemented on /gcs/ files yet")
+}
diff --git a/vendor/go4.org/wkfs/gcs/gcs_test.go b/vendor/go4.org/wkfs/gcs/gcs_test.go
new file mode 100644
index 0000000..ca17e7b
--- /dev/null
+++ b/vendor/go4.org/wkfs/gcs/gcs_test.go
@@ -0,0 +1,86 @@
+/*
+Copyright 2015 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package gcs
+
+import (
+ "bytes"
+ "flag"
+ "io"
+ "strings"
+ "testing"
+
+ "cloud.google.com/go/compute/metadata"
+ "cloud.google.com/go/storage"
+ "go4.org/wkfs"
+ "golang.org/x/net/context"
+ "google.golang.org/api/iterator"
+)
+
+var flagBucket = flag.String("bucket", "", "Google Cloud Storage bucket where to run the tests. It should be empty.")
+
+func TestWriteRead(t *testing.T) {
+ if !metadata.OnGCE() {
+ t.Skipf("Not testing on GCE")
+ }
+ if *flagBucket == "" {
+ t.Skipf("No bucket specified")
+ }
+ ctx := context.Background()
+ cl, err := storage.NewClient(ctx)
+ it := cl.Bucket(*flagBucket).Objects(ctx, nil)
+ if _, err := it.Next(); err != iterator.Done {
+ if err == nil {
+ t.Fatalf("Bucket %v is not empty, aborting test.", *flagBucket)
+ }
+ t.Fatalf("unexpected bucket iteration error: %v", err)
+ }
+
+ // Write to camli-gcs_test.txt
+ filename := "camli-gcs_test.txt"
+ gcsPath := "/gcs/" + *flagBucket + "/" + filename
+ f, err := wkfs.Create(gcsPath)
+ if err != nil {
+ t.Fatalf("error creating %v: %v", gcsPath, err)
+ }
+ defer func() {
+ if err := wkfs.Remove(gcsPath); err != nil {
+ t.Fatalf("error while cleaning up %v: %v", gcsPath, err)
+ }
+ }()
+
+ data := "Hello World"
+ if _, err := io.Copy(f, strings.NewReader(data)); err != nil {
+ t.Fatalf("error writing to %v: %v", gcsPath, err)
+ }
+ if err := f.Close(); err != nil {
+ t.Fatalf("error closing %v: %v", gcsPath, err)
+ }
+
+ // Read back from camli-gcs_test.txt
+ g, err := wkfs.Open(gcsPath)
+ if err != nil {
+ t.Fatalf("error opening %v: %v", gcsPath, err)
+ }
+ defer g.Close()
+ var buf bytes.Buffer
+ if _, err := io.Copy(&buf, g); err != nil {
+ t.Fatalf("error reading %v: %v", gcsPath, err)
+ }
+ if buf.String() != data {
+ t.Fatalf("error with %v contents: got %v, wanted %v", gcsPath, buf.String(), data)
+ }
+}
diff --git a/vendor/go4.org/wkfs/wkfs.go b/vendor/go4.org/wkfs/wkfs.go
new file mode 100644
index 0000000..ab67690
--- /dev/null
+++ b/vendor/go4.org/wkfs/wkfs.go
@@ -0,0 +1,135 @@
+/*
+Copyright 2014 The Perkeep Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package wkfs implements the pluggable "well-known filesystem" abstraction layer.
+//
+// Instead of accessing files directly through the operating system
+// using os.Open or os.Stat, code should use wkfs.Open or wkfs.Stat,
+// which first try to intercept paths at well-known top-level
+// directories representing previously-registered mount types,
+// otherwise fall through to the operating system paths.
+//
+// Example of top-level well-known directories that might be
+// registered include /gcs/bucket/object for Google Cloud Storage or
+// /s3/bucket/object for AWS S3.
+package wkfs // import "go4.org/wkfs"
+
+import (
+ "io"
+ "io/ioutil"
+ "os"
+ "strings"
+)
+
+type File interface {
+ io.Reader
+ io.ReaderAt
+ io.Closer
+ io.Seeker
+ Name() string
+ Stat() (os.FileInfo, error)
+}
+
+type FileWriter interface {
+ io.Writer
+ io.Closer
+}
+
+func Open(name string) (File, error) { return fs(name).Open(name) }
+func Stat(name string) (os.FileInfo, error) { return fs(name).Stat(name) }
+func Lstat(name string) (os.FileInfo, error) { return fs(name).Lstat(name) }
+func MkdirAll(path string, perm os.FileMode) error { return fs(path).MkdirAll(path, perm) }
+func OpenFile(name string, flag int, perm os.FileMode) (FileWriter, error) {
+ return fs(name).OpenFile(name, flag, perm)
+}
+func Remove(name string) error { return fs(name).Remove(name) }
+func Create(name string) (FileWriter, error) {
+ // like os.Create but WRONLY instead of RDWR because we don't
+ // expose a Reader here.
+ return OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
+}
+
+func fs(name string) FileSystem {
+ for pfx, fs := range wkFS {
+ if strings.HasPrefix(name, pfx) {
+ return fs
+ }
+ }
+ return osFS{}
+}
+
+type osFS struct{}
+
+func (osFS) Open(name string) (File, error) { return os.Open(name) }
+func (osFS) Stat(name string) (os.FileInfo, error) { return os.Stat(name) }
+func (osFS) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) }
+func (osFS) MkdirAll(path string, perm os.FileMode) error { return os.MkdirAll(path, perm) }
+func (osFS) OpenFile(name string, flag int, perm os.FileMode) (FileWriter, error) {
+ return os.OpenFile(name, flag, perm)
+}
+func (osFS) Remove(name string) error { return os.Remove(name) }
+
+type FileSystem interface {
+ Open(name string) (File, error)
+ OpenFile(name string, flag int, perm os.FileMode) (FileWriter, error)
+ Stat(name string) (os.FileInfo, error)
+ Lstat(name string) (os.FileInfo, error)
+ MkdirAll(path string, perm os.FileMode) error
+ Remove(name string) error
+}
+
+// well-known filesystems
+var wkFS = map[string]FileSystem{}
+
+// RegisterFS registers a well-known filesystem. It intercepts
+// anything beginning with prefix (which must start and end with a
+// forward slash) and forwards it to fs.
+func RegisterFS(prefix string, fs FileSystem) {
+ if !strings.HasPrefix(prefix, "/") || !strings.HasSuffix(prefix, "/") {
+ panic("bogus prefix: " + prefix)
+ }
+ if _, dup := wkFS[prefix]; dup {
+ panic("duplication registration of " + prefix)
+ }
+ wkFS[prefix] = fs
+}
+
+// WriteFile writes data to a file named by filename.
+// If the file does not exist, WriteFile creates it with permissions perm;
+// otherwise WriteFile truncates it before writing.
+func WriteFile(filename string, data []byte, perm os.FileMode) error {
+ f, err := OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
+ if err != nil {
+ return err
+ }
+ n, err := f.Write(data)
+ if err == nil && n < len(data) {
+ err = io.ErrShortWrite
+ }
+ if err1 := f.Close(); err == nil {
+ err = err1
+ }
+ return err
+}
+
+func ReadFile(filename string) ([]byte, error) {
+ f, err := Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return ioutil.ReadAll(f)
+}
diff --git a/vendor/go4.org/writerutil/writerutil.go b/vendor/go4.org/writerutil/writerutil.go
new file mode 100644
index 0000000..5c209cc
--- /dev/null
+++ b/vendor/go4.org/writerutil/writerutil.go
@@ -0,0 +1,105 @@
+/*
+Copyright 2016 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package writerutil contains io.Writer types.
+package writerutil // import "go4.org/writerutil"
+
+import (
+ "bytes"
+ "strconv"
+)
+
+// PrefixSuffixSaver is an io.Writer which retains the first N bytes
+// and the last N bytes written to it. The Bytes method reconstructs
+// it with a pretty error message.
+// It is copied from os/exec/exec.go of the Go stdlib.
+type PrefixSuffixSaver struct {
+ N int // max size of prefix or suffix
+ prefix []byte
+ suffix []byte // ring buffer once len(suffix) == N
+ suffixOff int // offset to write into suffix
+ skipped int64
+
+ // TODO(bradfitz): we could keep one large []byte and use part of it for
+ // the prefix, reserve space for the '... Omitting N bytes ...' message,
+ // then the ring buffer suffix, and just rearrange the ring buffer
+ // suffix when Bytes() is called, but it doesn't seem worth it for
+ // now just for error messages. It's only ~64KB anyway.
+}
+
+func (w *PrefixSuffixSaver) Write(p []byte) (n int, err error) {
+ lenp := len(p)
+ p = w.fill(&w.prefix, p)
+
+ // Only keep the last w.N bytes of suffix data.
+ if overage := len(p) - w.N; overage > 0 {
+ p = p[overage:]
+ w.skipped += int64(overage)
+ }
+ p = w.fill(&w.suffix, p)
+
+ // w.suffix is full now if p is non-empty. Overwrite it in a circle.
+ for len(p) > 0 { // 0, 1, or 2 iterations.
+ n := copy(w.suffix[w.suffixOff:], p)
+ p = p[n:]
+ w.skipped += int64(n)
+ w.suffixOff += n
+ if w.suffixOff == w.N {
+ w.suffixOff = 0
+ }
+ }
+ return lenp, nil
+}
+
+// fill appends up to len(p) bytes of p to *dst, such that *dst does not
+// grow larger than w.N. It returns the un-appended suffix of p.
+func (w *PrefixSuffixSaver) fill(dst *[]byte, p []byte) (pRemain []byte) {
+ if remain := w.N - len(*dst); remain > 0 {
+ add := minInt(len(p), remain)
+ *dst = append(*dst, p[:add]...)
+ p = p[add:]
+ }
+ return p
+}
+
+// Bytes returns a slice of the bytes, or a copy of the bytes, retained by w.
+// If more bytes than could be retained were written to w, it returns a
+// concatenation of the N first bytes, a message for how many bytes were dropped,
+// and the N last bytes.
+func (w *PrefixSuffixSaver) Bytes() []byte {
+ if w.suffix == nil {
+ return w.prefix
+ }
+ if w.skipped == 0 {
+ return append(w.prefix, w.suffix...)
+ }
+ var buf bytes.Buffer
+ buf.Grow(len(w.prefix) + len(w.suffix) + 50)
+ buf.Write(w.prefix)
+ buf.WriteString("\n... omitting ")
+ buf.WriteString(strconv.FormatInt(w.skipped, 10))
+ buf.WriteString(" bytes ...\n")
+ buf.Write(w.suffix[w.suffixOff:])
+ buf.Write(w.suffix[:w.suffixOff])
+ return buf.Bytes()
+}
+
+func minInt(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
diff --git a/vendor/go4.org/writerutil/writerutil_test.go b/vendor/go4.org/writerutil/writerutil_test.go
new file mode 100644
index 0000000..fae647a
--- /dev/null
+++ b/vendor/go4.org/writerutil/writerutil_test.go
@@ -0,0 +1,73 @@
+/*
+Copyright 2016 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package writerutil
+
+import (
+ "io"
+ "testing"
+)
+
+func TestPrefixSuffixSaver(t *testing.T) {
+ tests := []struct {
+ N int
+ writes []string
+ want string
+ }{
+ {
+ N: 2,
+ writes: nil,
+ want: "",
+ },
+ {
+ N: 2,
+ writes: []string{"a"},
+ want: "a",
+ },
+ {
+ N: 2,
+ writes: []string{"abc", "d"},
+ want: "abcd",
+ },
+ {
+ N: 2,
+ writes: []string{"abc", "d", "e"},
+ want: "ab\n... omitting 1 bytes ...\nde",
+ },
+ {
+ N: 2,
+ writes: []string{"ab______________________yz"},
+ want: "ab\n... omitting 22 bytes ...\nyz",
+ },
+ {
+ N: 2,
+ writes: []string{"ab_______________________y", "z"},
+ want: "ab\n... omitting 23 bytes ...\nyz",
+ },
+ }
+ for i, tt := range tests {
+ w := &PrefixSuffixSaver{N: tt.N}
+ for _, s := range tt.writes {
+ n, err := io.WriteString(w, s)
+ if err != nil || n != len(s) {
+ t.Errorf("%d. WriteString(%q) = %v, %v; want %v, %v", i, s, n, err, len(s), nil)
+ }
+ }
+ if got := string(w.Bytes()); got != tt.want {
+ t.Errorf("%d. Bytes = %q; want %q", i, got, tt.want)
+ }
+ }
+}
diff --git a/vendor/go4.org/xdgdir/example_test.go b/vendor/go4.org/xdgdir/example_test.go
new file mode 100644
index 0000000..1c9e143
--- /dev/null
+++ b/vendor/go4.org/xdgdir/example_test.go
@@ -0,0 +1,50 @@
+/*
+Copyright 2017 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package xdgdir_test
+
+import (
+ "fmt"
+ "os"
+
+ "go4.org/xdgdir"
+)
+
+func Example() {
+ // Print the absolute path of the current user's XDG_CONFIG_DIR.
+ fmt.Println(xdgdir.Config.Path())
+
+ // Read a file from $XDG_CONFIG_DIR/myconfig.json.
+ // This will search for a file named "myconfig.json" inside
+ // $XDG_CONFIG_DIR and then each entry inside $XDG_CONFIG_DIRS.
+ // It opens and returns the first file it finds, or returns an error.
+ if f, err := xdgdir.Data.Create("myconfig.json"); err == nil {
+ fmt.Fprintln(f, "Hello, World!")
+ if err := f.Close(); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ }
+ } else {
+ fmt.Fprintln(os.Stderr, err)
+ }
+
+ // Write a file to $XDG_DATA_DIR/myapp/foo.txt
+ if f, err := xdgdir.Data.Create("myapp/foo.txt"); err == nil {
+ fmt.Fprintln(f, "Hello, World!")
+ f.Close()
+ } else {
+ fmt.Fprintln(os.Stderr, err)
+ }
+}
diff --git a/vendor/go4.org/xdgdir/xdgdir.go b/vendor/go4.org/xdgdir/xdgdir.go
new file mode 100644
index 0000000..e9c3df6
--- /dev/null
+++ b/vendor/go4.org/xdgdir/xdgdir.go
@@ -0,0 +1,240 @@
+/*
+Copyright 2017 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package xdgdir implements the Free Desktop Base Directory
+// specification for locating directories.
+//
+// The specification is at
+// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+package xdgdir // import "go4.org/xdgdir"
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "os/user"
+ "path/filepath"
+ "syscall"
+)
+
+// Directories defined by the specification.
+var (
+ Data Dir
+ Config Dir
+ Cache Dir
+ Runtime Dir
+)
+
+func init() {
+ // Placed in init for the sake of readable docs.
+ Data = Dir{
+ env: "XDG_DATA_HOME",
+ dirsEnv: "XDG_DATA_DIRS",
+ fallback: ".local/share",
+ dirsFallback: []string{"/usr/local/share", "/usr/share"},
+ }
+ Config = Dir{
+ env: "XDG_CONFIG_HOME",
+ dirsEnv: "XDG_CONFIG_DIRS",
+ fallback: ".config",
+ dirsFallback: []string{"/etc/xdg"},
+ }
+ Cache = Dir{
+ env: "XDG_CACHE_HOME",
+ fallback: ".cache",
+ }
+ Runtime = Dir{
+ env: "XDG_RUNTIME_DIR",
+ userOwned: true,
+ }
+}
+
+// A Dir is a logical base directory along with additional search
+// directories.
+type Dir struct {
+ // env is the name of the environment variable for the base directory
+ // relative to which files should be written.
+ env string
+
+ // dirsEnv is the name of the environment variable containing
+ // preference-ordered base directories to search for files.
+ dirsEnv string
+
+ // fallback is the home-relative path to use if the variable named by
+ // env is not set.
+ fallback string
+
+ // dirsFallback is the list of paths to use if the variable named by
+ // dirsEnv is not set.
+ dirsFallback []string
+
+ // If userOwned is true, then for the directory to be considered
+ // valid, it must be owned by the user with the mode 700. This is
+ // only used for XDG_RUNTIME_DIR.
+ userOwned bool
+}
+
+// String returns the name of the primary environment variable for the
+// directory.
+func (d Dir) String() string {
+ if d.env == "" {
+ panic("xdgdir.Dir.String() on zero Dir")
+ }
+ return d.env
+}
+
+// Path returns the absolute path of the primary directory, or an empty
+// string if there's no suitable directory present. This is the path
+// that should be used for writing files.
+func (d Dir) Path() string {
+ if d.env == "" {
+ panic("xdgdir.Dir.Path() on zero Dir")
+ }
+ p := d.path()
+ if p != "" && d.userOwned {
+ info, err := os.Stat(p)
+ if err != nil {
+ return ""
+ }
+ if !info.IsDir() || info.Mode().Perm() != 0700 {
+ return ""
+ }
+ st, ok := info.Sys().(*syscall.Stat_t)
+ if !ok || int(st.Uid) != geteuid() {
+ return ""
+ }
+ }
+ return p
+}
+
+func (d Dir) path() string {
+ if e := getenv(d.env); isValidPath(e) {
+ return e
+ }
+ if d.fallback == "" {
+ return ""
+ }
+ home := findHome()
+ if home == "" {
+ return ""
+ }
+ p := filepath.Join(home, d.fallback)
+ if !isValidPath(p) {
+ return ""
+ }
+ return p
+}
+
+// SearchPaths returns the list of paths (in descending order of
+// preference) to search for files.
+func (d Dir) SearchPaths() []string {
+ if d.env == "" {
+ panic("xdgdir.Dir.SearchPaths() on zero Dir")
+ }
+ var paths []string
+ if p := d.Path(); p != "" {
+ paths = append(paths, p)
+ }
+ if d.dirsEnv == "" {
+ return paths
+ }
+ e := getenv(d.dirsEnv)
+ if e == "" {
+ paths = append(paths, d.dirsFallback...)
+ return paths
+ }
+ epaths := filepath.SplitList(e)
+ n := 0
+ for _, p := range epaths {
+ if isValidPath(p) {
+ epaths[n] = p
+ n++
+ }
+ }
+ paths = append(paths, epaths[:n]...)
+ return paths
+}
+
+// Open opens the named file inside the directory for reading. If the
+// directory has multiple search paths, each path is checked in order
+// for the file and the first one found is opened.
+func (d Dir) Open(name string) (*os.File, error) {
+ if d.env == "" {
+ return nil, errors.New("xdgdir: Open on zero Dir")
+ }
+ paths := d.SearchPaths()
+ if len(paths) == 0 {
+ return nil, fmt.Errorf("xdgdir: open %s: %s is invalid or not set", name, d.env)
+ }
+ var firstErr error
+ for _, p := range paths {
+ f, err := os.Open(filepath.Join(p, name))
+ if err == nil {
+ return f, nil
+ } else if !os.IsNotExist(err) {
+ firstErr = err
+ }
+ }
+ if firstErr != nil {
+ return nil, firstErr
+ }
+ return nil, &os.PathError{
+ Op: "Open",
+ Path: filepath.Join("$"+d.env, name),
+ Err: os.ErrNotExist,
+ }
+}
+
+// Create creates the named file inside the directory mode 0666 (before
+// umask), truncating it if it already exists. Parent directories of
+// the file will be created with mode 0700.
+func (d Dir) Create(name string) (*os.File, error) {
+ if d.env == "" {
+ return nil, errors.New("xdgdir: Create on zero Dir")
+ }
+ p := d.Path()
+ if p == "" {
+ return nil, fmt.Errorf("xdgdir: create %s: %s is invalid or not set", name, d.env)
+ }
+ fp := filepath.Join(p, name)
+ if err := os.MkdirAll(filepath.Dir(fp), 0700); err != nil {
+ return nil, err
+ }
+ return os.Create(fp)
+}
+
+func isValidPath(path string) bool {
+ return path != "" && filepath.IsAbs(path)
+}
+
+// findHome returns the user's home directory or the empty string if it
+// can't be found. It can be faked for testing.
+var findHome = func() string {
+ if h := getenv("HOME"); h != "" {
+ return h
+ }
+ u, err := user.Current()
+ if err != nil {
+ return ""
+ }
+ return u.HomeDir
+}
+
+// getenv retrieves an environment variable. It can be faked for testing.
+var getenv = os.Getenv
+
+// geteuid retrieves the effective user ID of the process. It can be faked for testing.
+var geteuid = os.Geteuid
diff --git a/vendor/go4.org/xdgdir/xdgdir_test.go b/vendor/go4.org/xdgdir/xdgdir_test.go
new file mode 100644
index 0000000..a1201d8
--- /dev/null
+++ b/vendor/go4.org/xdgdir/xdgdir_test.go
@@ -0,0 +1,494 @@
+/*
+Copyright 2017 The go4 Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package xdgdir
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func TestDir_Path(t *testing.T) {
+ td := newTempDir(t)
+ defer td.cleanup()
+ allopenDir := td.mkdir("allopen", 0777)
+ readonlyDir := td.mkdir("readonly", 0400)
+ secureDir := td.mkdir("secure", 0700)
+
+ tests := []struct {
+ dir Dir
+ env env
+ path string
+ geteuid func() int
+ }{
+ {
+ dir: Data,
+ env: env{"HOME": "/xHOMEx/me", "XDG_DATA_HOME": "/foo/data"},
+ path: "/foo/data",
+ },
+ {
+ dir: Data,
+ env: env{"HOME": "/xHOMEx/me"},
+ path: "/xHOMEx/me/.local/share",
+ },
+ {
+ dir: Data,
+ env: env{"HOME": "/xHOMEx/me", "XDG_DATA_HOME": "relative/path"},
+ path: "/xHOMEx/me/.local/share",
+ },
+ {
+ dir: Data,
+ env: env{},
+ path: "",
+ },
+ {
+ dir: Data,
+ env: env{"HOME": "relative/path"},
+ path: "",
+ },
+ {
+ dir: Config,
+ env: env{"HOME": "/xHOMEx/me", "XDG_CONFIG_HOME": "/foo/config"},
+ path: "/foo/config",
+ },
+ {
+ dir: Config,
+ env: env{"HOME": "/xHOMEx/me"},
+ path: "/xHOMEx/me/.config",
+ },
+ {
+ dir: Config,
+ env: env{"HOME": "/xHOMEx/me", "XDG_CONFIG_HOME": "relative/path"},
+ path: "/xHOMEx/me/.config",
+ },
+ {
+ dir: Config,
+ env: env{},
+ path: "",
+ },
+ {
+ dir: Cache,
+ env: env{"HOME": "/xHOMEx/me", "XDG_CACHE_HOME": "/foo/cache"},
+ path: "/foo/cache",
+ },
+ {
+ dir: Cache,
+ env: env{"HOME": "/xHOMEx/me"},
+ path: "/xHOMEx/me/.cache",
+ },
+ {
+ dir: Cache,
+ env: env{"HOME": "/xHOMEx/me", "XDG_CACHE_HOME": "relative/path"},
+ path: "/xHOMEx/me/.cache",
+ },
+ {
+ dir: Cache,
+ env: env{},
+ path: "",
+ },
+ {
+ dir: Runtime,
+ env: env{"XDG_RUNTIME_DIR": secureDir},
+ path: secureDir,
+ },
+ {
+ dir: Runtime,
+ env: env{"XDG_RUNTIME_DIR": secureDir},
+ geteuid: func() int { return os.Geteuid() + 1 },
+ path: "",
+ },
+ {
+ dir: Runtime,
+ env: env{"XDG_RUNTIME_DIR": readonlyDir},
+ path: "",
+ },
+ {
+ dir: Runtime,
+ env: env{"XDG_RUNTIME_DIR": allopenDir},
+ path: "",
+ },
+ {
+ dir: Runtime,
+ env: env{"HOME": secureDir},
+ path: "",
+ },
+ {
+ dir: Runtime,
+ env: env{},
+ path: "",
+ },
+ }
+ for _, test := range tests {
+ test.env.set()
+ if test.geteuid != nil {
+ geteuid = test.geteuid
+ } else {
+ geteuid = os.Geteuid
+ }
+ if path := test.dir.Path(); path != test.path {
+ var euidMod string
+ if test.geteuid != nil {
+ euidMod = " (euid modified)"
+ }
+ t.Errorf("In environment %v%s, %v.Path() = %q; want %q", test.env, euidMod, test.dir, path, test.path)
+ }
+ }
+}
+
+func TestDir_SearchPaths(t *testing.T) {
+ td := newTempDir(t)
+ defer td.cleanup()
+ allopenDir := td.mkdir("allopen", 0777)
+ secureDir := td.mkdir("secure", 0700)
+
+ tests := []struct {
+ dir Dir
+ env env
+ paths []string
+ }{
+ {
+ dir: Data,
+ env: env{},
+ paths: []string{"/usr/local/share", "/usr/share"},
+ },
+ {
+ dir: Data,
+ env: env{"HOME": "/xHOMEx/me"},
+ paths: []string{"/xHOMEx/me/.local/share", "/usr/local/share", "/usr/share"},
+ },
+ {
+ dir: Data,
+ env: env{"HOME": "/xHOMEx/me", "XDG_DATA_HOME": "/foo/data"},
+ paths: []string{"/foo/data", "/usr/local/share", "/usr/share"},
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": "/foo/data", "XDG_DATA_DIRS": "/mybacon/data"},
+ paths: []string{"/foo/data", "/mybacon/data"},
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": "/foo/data", "XDG_DATA_DIRS": "/mybacon/data:/eggs/data"},
+ paths: []string{"/foo/data", "/mybacon/data", "/eggs/data"},
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": "/foo/data", "XDG_DATA_DIRS": "/mybacon/data:/eggs/data:/woka/woka"},
+ paths: []string{"/foo/data", "/mybacon/data", "/eggs/data", "/woka/woka"},
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": "/foo/data", "XDG_DATA_DIRS": "/mybacon/data:relative/path:/woka/woka"},
+ paths: []string{"/foo/data", "/mybacon/data", "/woka/woka"},
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": "relative/path", "XDG_DATA_DIRS": "/mybacon/data:relative/path:/woka/woka"},
+ paths: []string{"/mybacon/data", "/woka/woka"},
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_DIRS": "/mybacon/data:/eggs/data:/woka/woka"},
+ paths: []string{"/mybacon/data", "/eggs/data", "/woka/woka"},
+ },
+ {
+ dir: Config,
+ env: env{"XDG_CONFIG_HOME": "/foo/config", "XDG_CONFIG_DIRS": "/mybacon/config:/eggs/config:/woka/woka"},
+ paths: []string{"/foo/config", "/mybacon/config", "/eggs/config", "/woka/woka"},
+ },
+ {
+ // Cache only has primary dir
+ dir: Cache,
+ env: env{"XDG_CACHE_HOME": "/foo/cache", "XDG_CACHE_DIRS": "/mybacon/config:/eggs/config:/woka/woka"},
+ paths: []string{"/foo/cache"},
+ },
+ {
+ dir: Runtime,
+ env: env{"XDG_RUNTIME_DIR": secureDir},
+ paths: []string{secureDir},
+ },
+ {
+ dir: Runtime,
+ env: env{"XDG_RUNTIME_DIR": allopenDir},
+ paths: []string{},
+ },
+ }
+ for _, test := range tests {
+ test.env.set()
+ paths := test.dir.SearchPaths()
+ if !stringsEqual(paths, test.paths) {
+ t.Errorf("In environment %v, %v.SearchPaths() = %q; want %q", test.env, test.dir, paths, test.paths)
+ }
+ }
+}
+
+func TestDir_Open(t *testing.T) {
+ td := newTempDir(t)
+ defer td.cleanup()
+ junkDir := td.mkdir("junk", 0777)
+ dir1 := td.mkdir("dir1", 0777)
+ dir2 := td.mkdir("dir2", 0777)
+ dir3 := td.mkdir("dir3", 0777)
+ td.newFile("dir1/foo.txt", "foo")
+ td.newFile("dir1/multiple.txt", "1")
+ td.newFile("dir2/bar.txt", "bar")
+ td.newFile("dir2/only2_3.txt", "this is 2")
+ td.newFile("dir2/multiple.txt", "2")
+ td.newFile("dir3/multiple.txt", "3")
+ td.newFile("dir3/only2_3.txt", "this is 3")
+
+ tests := []struct {
+ dir Dir
+ env env
+ name string
+
+ path string
+ err bool
+ }{
+ {
+ dir: Data,
+ env: env{},
+ name: "foo.txt",
+ err: true,
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": junkDir},
+ name: "foo.txt",
+ path: filepath.Join(dir1, "foo.txt"),
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": junkDir, "XDG_DATA_DIRS": junkDir},
+ name: "foo.txt",
+ err: true,
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2},
+ name: "foo.txt",
+ path: filepath.Join(dir1, "foo.txt"),
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2},
+ name: "bar.txt",
+ path: filepath.Join(dir2, "bar.txt"),
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3},
+ name: "NOTREAL.txt",
+ err: true,
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3},
+ name: "foo.txt",
+ path: filepath.Join(dir1, "foo.txt"),
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3},
+ name: "bar.txt",
+ path: filepath.Join(dir2, "bar.txt"),
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3},
+ name: "multiple.txt",
+ path: filepath.Join(dir1, "multiple.txt"),
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3},
+ name: "only2_3.txt",
+ path: filepath.Join(dir2, "only2_3.txt"),
+ },
+ }
+ for _, test := range tests {
+ test.env.set()
+ f, err := test.dir.Open(test.name)
+ switch {
+ case err == nil && test.err:
+ t.Errorf("In environment %v, %v.Open(%q) succeeded; want error", test.env, test.dir, test.name)
+ case err == nil && !test.err && f.Name() != test.path:
+ t.Errorf("In environment %v, %v.Open(%q).Name() = %q; want %q", test.env, test.dir, test.name, f.Name(), test.path)
+ case err != nil && !test.err:
+ t.Errorf("In environment %v, %v.Open(%q) error: %v", test.env, test.dir, test.name, err)
+ }
+ if f != nil {
+ f.Close()
+ }
+ }
+}
+
+func TestDir_Create(t *testing.T) {
+ td := newTempDir(t)
+ defer td.cleanup()
+ junkDir := td.mkdir("junk", 0777)
+ dataDir := td.mkdir("data", 0777)
+
+ tests := []struct {
+ dir Dir
+ env env
+ name string
+
+ path string
+ err bool
+ permChecks []permCheck
+ }{
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": dataDir, "XDG_DATA_DIRS": junkDir},
+ name: "foo01",
+ path: filepath.Join(dataDir, "foo01"),
+ },
+ {
+ dir: Data,
+ env: env{},
+ name: "foo02",
+ err: true,
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": dataDir, "XDG_DATA_DIRS": junkDir},
+ name: filepath.Join("foo03", "bar"),
+ path: filepath.Join(dataDir, "foo03", "bar"),
+ permChecks: []permCheck{
+ {filepath.Join(dataDir, "foo03"), 0700},
+ },
+ },
+ {
+ dir: Data,
+ env: env{"XDG_DATA_HOME": filepath.Join(td.dir, "NOTREAL"), "XDG_DATA_DIRS": junkDir},
+ name: filepath.Join("foo04", "bar"),
+ path: filepath.Join(td.dir, "NOTREAL", "foo04", "bar"),
+ permChecks: []permCheck{
+ {filepath.Join(td.dir, "NOTREAL"), 0700},
+ {filepath.Join(td.dir, "NOTREAL", "foo04"), 0700},
+ },
+ },
+ }
+ for _, test := range tests {
+ test.env.set()
+ f, err := test.dir.Create(test.name)
+ switch {
+ case err == nil && test.err:
+ t.Errorf("In environment %v, %v.Create(%q) succeeded; want error", test.env, test.dir, test.name)
+ case err == nil && !test.err && f.Name() != test.path:
+ t.Errorf("In environment %v, %v.Create(%q).Name() = %q; want %q", test.env, test.dir, test.name, f.Name(), test.path)
+ case err != nil && !test.err:
+ t.Errorf("In environment %v, %v.Create(%q) error: %v", test.env, test.dir, test.name, err)
+ }
+ if f != nil {
+ f.Close()
+ }
+ for _, pc := range test.permChecks {
+ info, err := os.Stat(pc.name)
+ if err != nil {
+ t.Errorf("In environment %v, %v.Create(%q): stat %s error: %v", test.env, test.dir, test.name, pc.name, err)
+ continue
+ }
+ if perm := info.Mode().Perm(); perm != pc.perm {
+ t.Errorf("In environment %v, %v.Create(%q): %s has permission %v; want %v", test.env, test.dir, test.name, pc.name, perm, pc.perm)
+ }
+ }
+ }
+}
+
+func stringsEqual(a, b []string) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i := range a {
+ if a[i] != b[i] {
+ return false
+ }
+ }
+ return true
+}
+
+type tempDir struct {
+ t *testing.T
+ dir string
+}
+
+func newTempDir(t *testing.T) *tempDir {
+ td := &tempDir{t: t}
+ var err error
+ td.dir, err = ioutil.TempDir("", "xdgdir_test")
+ if err != nil {
+ t.Fatal("making temp dir:", err)
+ }
+ return td
+}
+
+// newFile creates a file and returns its path.
+func (td *tempDir) newFile(name string, data string) string {
+ path := filepath.Join(td.dir, name)
+ f, err := os.Create(path)
+ if err != nil {
+ td.t.Fatalf("newFile(%q, %q) error: %v", name, data, err)
+ }
+ _, werr := f.Write([]byte(data))
+ cerr := f.Close()
+ if werr != nil {
+ td.t.Errorf("newFile(%q, %q) write error: %v", name, data, err)
+ }
+ if cerr != nil {
+ td.t.Errorf("newFile(%q, %q) close error: %v", name, data, err)
+ }
+ if werr != nil || cerr != nil {
+ td.t.FailNow()
+ }
+ return path
+}
+
+// mkdir creates a directory and returns its path.
+func (td *tempDir) mkdir(name string, perm os.FileMode) string {
+ path := filepath.Join(td.dir, name)
+ err := os.Mkdir(path, perm)
+ if err != nil {
+ td.t.Fatal(err)
+ }
+ return path
+}
+
+func (td *tempDir) cleanup() {
+ err := os.RemoveAll(td.dir)
+ if err != nil {
+ td.t.Log("failed to clean up temp dir:", err)
+ }
+}
+
+type permCheck struct {
+ name string
+ perm os.FileMode
+}
+
+type env map[string]string
+
+func (e env) set() {
+ getenv = func(key string) string {
+ return e[key]
+ }
+ findHome = func() string {
+ return e["HOME"]
+ }
+}
diff --git a/vendor/golang.org/x/net/.gitattributes b/vendor/golang.org/x/net/.gitattributes
new file mode 100644
index 0000000..d2f212e
--- /dev/null
+++ b/vendor/golang.org/x/net/.gitattributes
@@ -0,0 +1,10 @@
+# Treat all files in this repo as binary, with no git magic updating
+# line endings. Windows users contributing to Go will need to use a
+# modern version of git and editors capable of LF line endings.
+#
+# We'll prevent accidental CRLF line endings from entering the repo
+# via the git-review gofmt checks.
+#
+# See golang.org/issue/9281
+
+* -text
diff --git a/vendor/golang.org/x/net/.gitignore b/vendor/golang.org/x/net/.gitignore
new file mode 100644
index 0000000..8339fd6
--- /dev/null
+++ b/vendor/golang.org/x/net/.gitignore
@@ -0,0 +1,2 @@
+# Add no patterns to .hgignore except for files generated by the build.
+last-change
diff --git a/vendor/golang.org/x/net/AUTHORS b/vendor/golang.org/x/net/AUTHORS
new file mode 100644
index 0000000..15167cd
--- /dev/null
+++ b/vendor/golang.org/x/net/AUTHORS
@@ -0,0 +1,3 @@
+# This source code refers to The Go Authors for copyright purposes.
+# The master list of authors is in the main Go distribution,
+# visible at http://tip.golang.org/AUTHORS.
diff --git a/vendor/golang.org/x/net/CONTRIBUTING.md b/vendor/golang.org/x/net/CONTRIBUTING.md
new file mode 100644
index 0000000..d0485e8
--- /dev/null
+++ b/vendor/golang.org/x/net/CONTRIBUTING.md
@@ -0,0 +1,26 @@
+# Contributing to Go
+
+Go is an open source project.
+
+It is the work of hundreds of contributors. We appreciate your help!
+
+## Filing issues
+
+When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
+
+1. What version of Go are you using (`go version`)?
+2. What operating system and processor architecture are you using?
+3. What did you do?
+4. What did you expect to see?
+5. What did you see instead?
+
+General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
+The gophers there will answer or ask you to file an issue if you've tripped over a bug.
+
+## Contributing code
+
+Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
+before sending patches.
+
+Unless otherwise noted, the Go source files are distributed under
+the BSD-style license found in the LICENSE file.
diff --git a/vendor/golang.org/x/net/CONTRIBUTORS b/vendor/golang.org/x/net/CONTRIBUTORS
new file mode 100644
index 0000000..1c4577e
--- /dev/null
+++ b/vendor/golang.org/x/net/CONTRIBUTORS
@@ -0,0 +1,3 @@
+# This source code was written by the Go contributors.
+# The master list of contributors is in the main Go distribution,
+# visible at http://tip.golang.org/CONTRIBUTORS.
diff --git a/vendor/golang.org/x/net/LICENSE b/vendor/golang.org/x/net/LICENSE
new file mode 100644
index 0000000..6a66aea
--- /dev/null
+++ b/vendor/golang.org/x/net/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go 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/golang.org/x/net/PATENTS b/vendor/golang.org/x/net/PATENTS
new file mode 100644
index 0000000..7330990
--- /dev/null
+++ b/vendor/golang.org/x/net/PATENTS
@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Go project.
+
+Google hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section)
+patent license to make, have made, use, offer to sell, sell, import,
+transfer and otherwise run, modify and propagate the contents of this
+implementation of Go, where such license applies only to those patent
+claims, both currently owned or controlled by Google and acquired in
+the future, licensable by Google that are necessarily infringed by this
+implementation of Go. This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation. If you or your agent or exclusive licensee institute or
+order or agree to the institution of patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging
+that this implementation of Go or any code incorporated within this
+implementation of Go constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation of Go
+shall terminate as of the date such litigation is filed.
diff --git a/vendor/golang.org/x/net/README.md b/vendor/golang.org/x/net/README.md
new file mode 100644
index 0000000..00a9b6e
--- /dev/null
+++ b/vendor/golang.org/x/net/README.md
@@ -0,0 +1,16 @@
+# Go Networking
+
+This repository holds supplementary Go networking libraries.
+
+## Download/Install
+
+The easiest way to install is to run `go get -u golang.org/x/net`. You can
+also manually git clone the repository to `$GOPATH/src/golang.org/x/net`.
+
+## Report Issues / Send Patches
+
+This repository uses Gerrit for code changes. To learn how to submit
+changes to this repository, see https://golang.org/doc/contribute.html.
+The main issue tracker for the net repository is located at
+https://github.com/golang/go/issues. Prefix your issue with "x/net:" in the
+subject line, so it is easy to find.
diff --git a/vendor/golang.org/x/net/bpf/asm.go b/vendor/golang.org/x/net/bpf/asm.go
new file mode 100644
index 0000000..15e21b1
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/asm.go
@@ -0,0 +1,41 @@
+// Copyright 2016 The Go 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 bpf
+
+import "fmt"
+
+// Assemble converts insts into raw instructions suitable for loading
+// into a BPF virtual machine.
+//
+// Currently, no optimization is attempted, the assembled program flow
+// is exactly as provided.
+func Assemble(insts []Instruction) ([]RawInstruction, error) {
+ ret := make([]RawInstruction, len(insts))
+ var err error
+ for i, inst := range insts {
+ ret[i], err = inst.Assemble()
+ if err != nil {
+ return nil, fmt.Errorf("assembling instruction %d: %s", i+1, err)
+ }
+ }
+ return ret, nil
+}
+
+// Disassemble attempts to parse raw back into
+// Instructions. Unrecognized RawInstructions are assumed to be an
+// extension not implemented by this package, and are passed through
+// unchanged to the output. The allDecoded value reports whether insts
+// contains no RawInstructions.
+func Disassemble(raw []RawInstruction) (insts []Instruction, allDecoded bool) {
+ insts = make([]Instruction, len(raw))
+ allDecoded = true
+ for i, r := range raw {
+ insts[i] = r.Disassemble()
+ if _, ok := insts[i].(RawInstruction); ok {
+ allDecoded = false
+ }
+ }
+ return insts, allDecoded
+}
diff --git a/vendor/golang.org/x/net/bpf/constants.go b/vendor/golang.org/x/net/bpf/constants.go
new file mode 100644
index 0000000..b89ca35
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/constants.go
@@ -0,0 +1,218 @@
+// Copyright 2016 The Go 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 bpf
+
+// A Register is a register of the BPF virtual machine.
+type Register uint16
+
+const (
+ // RegA is the accumulator register. RegA is always the
+ // destination register of ALU operations.
+ RegA Register = iota
+ // RegX is the indirection register, used by LoadIndirect
+ // operations.
+ RegX
+)
+
+// An ALUOp is an arithmetic or logic operation.
+type ALUOp uint16
+
+// ALU binary operation types.
+const (
+ ALUOpAdd ALUOp = iota << 4
+ ALUOpSub
+ ALUOpMul
+ ALUOpDiv
+ ALUOpOr
+ ALUOpAnd
+ ALUOpShiftLeft
+ ALUOpShiftRight
+ aluOpNeg // Not exported because it's the only unary ALU operation, and gets its own instruction type.
+ ALUOpMod
+ ALUOpXor
+)
+
+// A JumpTest is a comparison operator used in conditional jumps.
+type JumpTest uint16
+
+// Supported operators for conditional jumps.
+const (
+ // K == A
+ JumpEqual JumpTest = iota
+ // K != A
+ JumpNotEqual
+ // K > A
+ JumpGreaterThan
+ // K < A
+ JumpLessThan
+ // K >= A
+ JumpGreaterOrEqual
+ // K <= A
+ JumpLessOrEqual
+ // K & A != 0
+ JumpBitsSet
+ // K & A == 0
+ JumpBitsNotSet
+)
+
+// An Extension is a function call provided by the kernel that
+// performs advanced operations that are expensive or impossible
+// within the BPF virtual machine.
+//
+// Extensions are only implemented by the Linux kernel.
+//
+// TODO: should we prune this list? Some of these extensions seem
+// either broken or near-impossible to use correctly, whereas other
+// (len, random, ifindex) are quite useful.
+type Extension int
+
+// Extension functions available in the Linux kernel.
+const (
+ // extOffset is the negative maximum number of instructions used
+ // to load instructions by overloading the K argument.
+ extOffset = -0x1000
+ // ExtLen returns the length of the packet.
+ ExtLen Extension = 1
+ // ExtProto returns the packet's L3 protocol type.
+ ExtProto Extension = 0
+ // ExtType returns the packet's type (skb->pkt_type in the kernel)
+ //
+ // TODO: better documentation. How nice an API do we want to
+ // provide for these esoteric extensions?
+ ExtType Extension = 4
+ // ExtPayloadOffset returns the offset of the packet payload, or
+ // the first protocol header that the kernel does not know how to
+ // parse.
+ ExtPayloadOffset Extension = 52
+ // ExtInterfaceIndex returns the index of the interface on which
+ // the packet was received.
+ ExtInterfaceIndex Extension = 8
+ // ExtNetlinkAttr returns the netlink attribute of type X at
+ // offset A.
+ ExtNetlinkAttr Extension = 12
+ // ExtNetlinkAttrNested returns the nested netlink attribute of
+ // type X at offset A.
+ ExtNetlinkAttrNested Extension = 16
+ // ExtMark returns the packet's mark value.
+ ExtMark Extension = 20
+ // ExtQueue returns the packet's assigned hardware queue.
+ ExtQueue Extension = 24
+ // ExtLinkLayerType returns the packet's hardware address type
+ // (e.g. Ethernet, Infiniband).
+ ExtLinkLayerType Extension = 28
+ // ExtRXHash returns the packets receive hash.
+ //
+ // TODO: figure out what this rxhash actually is.
+ ExtRXHash Extension = 32
+ // ExtCPUID returns the ID of the CPU processing the current
+ // packet.
+ ExtCPUID Extension = 36
+ // ExtVLANTag returns the packet's VLAN tag.
+ ExtVLANTag Extension = 44
+ // ExtVLANTagPresent returns non-zero if the packet has a VLAN
+ // tag.
+ //
+ // TODO: I think this might be a lie: it reads bit 0x1000 of the
+ // VLAN header, which changed meaning in recent revisions of the
+ // spec - this extension may now return meaningless information.
+ ExtVLANTagPresent Extension = 48
+ // ExtVLANProto returns 0x8100 if the frame has a VLAN header,
+ // 0x88a8 if the frame has a "Q-in-Q" double VLAN header, or some
+ // other value if no VLAN information is present.
+ ExtVLANProto Extension = 60
+ // ExtRand returns a uniformly random uint32.
+ ExtRand Extension = 56
+)
+
+// The following gives names to various bit patterns used in opcode construction.
+
+const (
+ opMaskCls uint16 = 0x7
+ // opClsLoad masks
+ opMaskLoadDest = 0x01
+ opMaskLoadWidth = 0x18
+ opMaskLoadMode = 0xe0
+ // opClsALU
+ opMaskOperandSrc = 0x08
+ opMaskOperator = 0xf0
+ // opClsJump
+ opMaskJumpConst = 0x0f
+ opMaskJumpCond = 0xf0
+)
+
+const (
+ // +---------------+-----------------+---+---+---+
+ // | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 0 |
+ // +---------------+-----------------+---+---+---+
+ opClsLoadA uint16 = iota
+ // +---------------+-----------------+---+---+---+
+ // | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 1 |
+ // +---------------+-----------------+---+---+---+
+ opClsLoadX
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
+ // +---+---+---+---+---+---+---+---+
+ opClsStoreA
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
+ // +---+---+---+---+---+---+---+---+
+ opClsStoreX
+ // +---------------+-----------------+---+---+---+
+ // | Operator (4b) | OperandSrc (1b) | 1 | 0 | 0 |
+ // +---------------+-----------------+---+---+---+
+ opClsALU
+ // +-----------------------------+---+---+---+---+
+ // | TestOperator (4b) | 0 | 1 | 0 | 1 |
+ // +-----------------------------+---+---+---+---+
+ opClsJump
+ // +---+-------------------------+---+---+---+---+
+ // | 0 | 0 | 0 | RetSrc (1b) | 0 | 1 | 1 | 0 |
+ // +---+-------------------------+---+---+---+---+
+ opClsReturn
+ // +---+-------------------------+---+---+---+---+
+ // | 0 | 0 | 0 | TXAorTAX (1b) | 0 | 1 | 1 | 1 |
+ // +---+-------------------------+---+---+---+---+
+ opClsMisc
+)
+
+const (
+ opAddrModeImmediate uint16 = iota << 5
+ opAddrModeAbsolute
+ opAddrModeIndirect
+ opAddrModeScratch
+ opAddrModePacketLen // actually an extension, not an addressing mode.
+ opAddrModeMemShift
+)
+
+const (
+ opLoadWidth4 uint16 = iota << 3
+ opLoadWidth2
+ opLoadWidth1
+)
+
+// Operator defined by ALUOp*
+
+const (
+ opALUSrcConstant uint16 = iota << 3
+ opALUSrcX
+)
+
+const (
+ opJumpAlways = iota << 4
+ opJumpEqual
+ opJumpGT
+ opJumpGE
+ opJumpSet
+)
+
+const (
+ opRetSrcConstant uint16 = iota << 4
+ opRetSrcA
+)
+
+const (
+ opMiscTAX = 0x00
+ opMiscTXA = 0x80
+)
diff --git a/vendor/golang.org/x/net/bpf/doc.go b/vendor/golang.org/x/net/bpf/doc.go
new file mode 100644
index 0000000..ae62feb
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/doc.go
@@ -0,0 +1,82 @@
+// Copyright 2016 The Go 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 bpf implements marshaling and unmarshaling of programs for the
+Berkeley Packet Filter virtual machine, and provides a Go implementation
+of the virtual machine.
+
+BPF's main use is to specify a packet filter for network taps, so that
+the kernel doesn't have to expensively copy every packet it sees to
+userspace. However, it's been repurposed to other areas where running
+user code in-kernel is needed. For example, Linux's seccomp uses BPF
+to apply security policies to system calls. For simplicity, this
+documentation refers only to packets, but other uses of BPF have their
+own data payloads.
+
+BPF programs run in a restricted virtual machine. It has almost no
+access to kernel functions, and while conditional branches are
+allowed, they can only jump forwards, to guarantee that there are no
+infinite loops.
+
+The virtual machine
+
+The BPF VM is an accumulator machine. Its main register, called
+register A, is an implicit source and destination in all arithmetic
+and logic operations. The machine also has 16 scratch registers for
+temporary storage, and an indirection register (register X) for
+indirect memory access. All registers are 32 bits wide.
+
+Each run of a BPF program is given one packet, which is placed in the
+VM's read-only "main memory". LoadAbsolute and LoadIndirect
+instructions can fetch up to 32 bits at a time into register A for
+examination.
+
+The goal of a BPF program is to produce and return a verdict (uint32),
+which tells the kernel what to do with the packet. In the context of
+packet filtering, the returned value is the number of bytes of the
+packet to forward to userspace, or 0 to ignore the packet. Other
+contexts like seccomp define their own return values.
+
+In order to simplify programs, attempts to read past the end of the
+packet terminate the program execution with a verdict of 0 (ignore
+packet). This means that the vast majority of BPF programs don't need
+to do any explicit bounds checking.
+
+In addition to the bytes of the packet, some BPF programs have access
+to extensions, which are essentially calls to kernel utility
+functions. Currently, the only extensions supported by this package
+are the Linux packet filter extensions.
+
+Examples
+
+This packet filter selects all ARP packets.
+
+ bpf.Assemble([]bpf.Instruction{
+ // Load "EtherType" field from the ethernet header.
+ bpf.LoadAbsolute{Off: 12, Size: 2},
+ // Skip over the next instruction if EtherType is not ARP.
+ bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x0806, SkipTrue: 1},
+ // Verdict is "send up to 4k of the packet to userspace."
+ bpf.RetConstant{Val: 4096},
+ // Verdict is "ignore packet."
+ bpf.RetConstant{Val: 0},
+ })
+
+This packet filter captures a random 1% sample of traffic.
+
+ bpf.Assemble([]bpf.Instruction{
+ // Get a 32-bit random number from the Linux kernel.
+ bpf.LoadExtension{Num: bpf.ExtRand},
+ // 1% dice roll?
+ bpf.JumpIf{Cond: bpf.JumpLessThan, Val: 2^32/100, SkipFalse: 1},
+ // Capture.
+ bpf.RetConstant{Val: 4096},
+ // Ignore.
+ bpf.RetConstant{Val: 0},
+ })
+
+*/
+package bpf // import "golang.org/x/net/bpf"
diff --git a/vendor/golang.org/x/net/bpf/instructions.go b/vendor/golang.org/x/net/bpf/instructions.go
new file mode 100644
index 0000000..f9dc0e8
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/instructions.go
@@ -0,0 +1,704 @@
+// Copyright 2016 The Go 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 bpf
+
+import "fmt"
+
+// An Instruction is one instruction executed by the BPF virtual
+// machine.
+type Instruction interface {
+ // Assemble assembles the Instruction into a RawInstruction.
+ Assemble() (RawInstruction, error)
+}
+
+// A RawInstruction is a raw BPF virtual machine instruction.
+type RawInstruction struct {
+ // Operation to execute.
+ Op uint16
+ // For conditional jump instructions, the number of instructions
+ // to skip if the condition is true/false.
+ Jt uint8
+ Jf uint8
+ // Constant parameter. The meaning depends on the Op.
+ K uint32
+}
+
+// Assemble implements the Instruction Assemble method.
+func (ri RawInstruction) Assemble() (RawInstruction, error) { return ri, nil }
+
+// Disassemble parses ri into an Instruction and returns it. If ri is
+// not recognized by this package, ri itself is returned.
+func (ri RawInstruction) Disassemble() Instruction {
+ switch ri.Op & opMaskCls {
+ case opClsLoadA, opClsLoadX:
+ reg := Register(ri.Op & opMaskLoadDest)
+ sz := 0
+ switch ri.Op & opMaskLoadWidth {
+ case opLoadWidth4:
+ sz = 4
+ case opLoadWidth2:
+ sz = 2
+ case opLoadWidth1:
+ sz = 1
+ default:
+ return ri
+ }
+ switch ri.Op & opMaskLoadMode {
+ case opAddrModeImmediate:
+ if sz != 4 {
+ return ri
+ }
+ return LoadConstant{Dst: reg, Val: ri.K}
+ case opAddrModeScratch:
+ if sz != 4 || ri.K > 15 {
+ return ri
+ }
+ return LoadScratch{Dst: reg, N: int(ri.K)}
+ case opAddrModeAbsolute:
+ if ri.K > extOffset+0xffffffff {
+ return LoadExtension{Num: Extension(-extOffset + ri.K)}
+ }
+ return LoadAbsolute{Size: sz, Off: ri.K}
+ case opAddrModeIndirect:
+ return LoadIndirect{Size: sz, Off: ri.K}
+ case opAddrModePacketLen:
+ if sz != 4 {
+ return ri
+ }
+ return LoadExtension{Num: ExtLen}
+ case opAddrModeMemShift:
+ return LoadMemShift{Off: ri.K}
+ default:
+ return ri
+ }
+
+ case opClsStoreA:
+ if ri.Op != opClsStoreA || ri.K > 15 {
+ return ri
+ }
+ return StoreScratch{Src: RegA, N: int(ri.K)}
+
+ case opClsStoreX:
+ if ri.Op != opClsStoreX || ri.K > 15 {
+ return ri
+ }
+ return StoreScratch{Src: RegX, N: int(ri.K)}
+
+ case opClsALU:
+ switch op := ALUOp(ri.Op & opMaskOperator); op {
+ case ALUOpAdd, ALUOpSub, ALUOpMul, ALUOpDiv, ALUOpOr, ALUOpAnd, ALUOpShiftLeft, ALUOpShiftRight, ALUOpMod, ALUOpXor:
+ if ri.Op&opMaskOperandSrc != 0 {
+ return ALUOpX{Op: op}
+ }
+ return ALUOpConstant{Op: op, Val: ri.K}
+ case aluOpNeg:
+ return NegateA{}
+ default:
+ return ri
+ }
+
+ case opClsJump:
+ if ri.Op&opMaskJumpConst != opClsJump {
+ return ri
+ }
+ switch ri.Op & opMaskJumpCond {
+ case opJumpAlways:
+ return Jump{Skip: ri.K}
+ case opJumpEqual:
+ if ri.Jt == 0 {
+ return JumpIf{
+ Cond: JumpNotEqual,
+ Val: ri.K,
+ SkipTrue: ri.Jf,
+ SkipFalse: 0,
+ }
+ }
+ return JumpIf{
+ Cond: JumpEqual,
+ Val: ri.K,
+ SkipTrue: ri.Jt,
+ SkipFalse: ri.Jf,
+ }
+ case opJumpGT:
+ if ri.Jt == 0 {
+ return JumpIf{
+ Cond: JumpLessOrEqual,
+ Val: ri.K,
+ SkipTrue: ri.Jf,
+ SkipFalse: 0,
+ }
+ }
+ return JumpIf{
+ Cond: JumpGreaterThan,
+ Val: ri.K,
+ SkipTrue: ri.Jt,
+ SkipFalse: ri.Jf,
+ }
+ case opJumpGE:
+ if ri.Jt == 0 {
+ return JumpIf{
+ Cond: JumpLessThan,
+ Val: ri.K,
+ SkipTrue: ri.Jf,
+ SkipFalse: 0,
+ }
+ }
+ return JumpIf{
+ Cond: JumpGreaterOrEqual,
+ Val: ri.K,
+ SkipTrue: ri.Jt,
+ SkipFalse: ri.Jf,
+ }
+ case opJumpSet:
+ return JumpIf{
+ Cond: JumpBitsSet,
+ Val: ri.K,
+ SkipTrue: ri.Jt,
+ SkipFalse: ri.Jf,
+ }
+ default:
+ return ri
+ }
+
+ case opClsReturn:
+ switch ri.Op {
+ case opClsReturn | opRetSrcA:
+ return RetA{}
+ case opClsReturn | opRetSrcConstant:
+ return RetConstant{Val: ri.K}
+ default:
+ return ri
+ }
+
+ case opClsMisc:
+ switch ri.Op {
+ case opClsMisc | opMiscTAX:
+ return TAX{}
+ case opClsMisc | opMiscTXA:
+ return TXA{}
+ default:
+ return ri
+ }
+
+ default:
+ panic("unreachable") // switch is exhaustive on the bit pattern
+ }
+}
+
+// LoadConstant loads Val into register Dst.
+type LoadConstant struct {
+ Dst Register
+ Val uint32
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a LoadConstant) Assemble() (RawInstruction, error) {
+ return assembleLoad(a.Dst, 4, opAddrModeImmediate, a.Val)
+}
+
+// String returns the instruction in assembler notation.
+func (a LoadConstant) String() string {
+ switch a.Dst {
+ case RegA:
+ return fmt.Sprintf("ld #%d", a.Val)
+ case RegX:
+ return fmt.Sprintf("ldx #%d", a.Val)
+ default:
+ return fmt.Sprintf("unknown instruction: %#v", a)
+ }
+}
+
+// LoadScratch loads scratch[N] into register Dst.
+type LoadScratch struct {
+ Dst Register
+ N int // 0-15
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a LoadScratch) Assemble() (RawInstruction, error) {
+ if a.N < 0 || a.N > 15 {
+ return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
+ }
+ return assembleLoad(a.Dst, 4, opAddrModeScratch, uint32(a.N))
+}
+
+// String returns the instruction in assembler notation.
+func (a LoadScratch) String() string {
+ switch a.Dst {
+ case RegA:
+ return fmt.Sprintf("ld M[%d]", a.N)
+ case RegX:
+ return fmt.Sprintf("ldx M[%d]", a.N)
+ default:
+ return fmt.Sprintf("unknown instruction: %#v", a)
+ }
+}
+
+// LoadAbsolute loads packet[Off:Off+Size] as an integer value into
+// register A.
+type LoadAbsolute struct {
+ Off uint32
+ Size int // 1, 2 or 4
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a LoadAbsolute) Assemble() (RawInstruction, error) {
+ return assembleLoad(RegA, a.Size, opAddrModeAbsolute, a.Off)
+}
+
+// String returns the instruction in assembler notation.
+func (a LoadAbsolute) String() string {
+ switch a.Size {
+ case 1: // byte
+ return fmt.Sprintf("ldb [%d]", a.Off)
+ case 2: // half word
+ return fmt.Sprintf("ldh [%d]", a.Off)
+ case 4: // word
+ if a.Off > extOffset+0xffffffff {
+ return LoadExtension{Num: Extension(a.Off + 0x1000)}.String()
+ }
+ return fmt.Sprintf("ld [%d]", a.Off)
+ default:
+ return fmt.Sprintf("unknown instruction: %#v", a)
+ }
+}
+
+// LoadIndirect loads packet[X+Off:X+Off+Size] as an integer value
+// into register A.
+type LoadIndirect struct {
+ Off uint32
+ Size int // 1, 2 or 4
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a LoadIndirect) Assemble() (RawInstruction, error) {
+ return assembleLoad(RegA, a.Size, opAddrModeIndirect, a.Off)
+}
+
+// String returns the instruction in assembler notation.
+func (a LoadIndirect) String() string {
+ switch a.Size {
+ case 1: // byte
+ return fmt.Sprintf("ldb [x + %d]", a.Off)
+ case 2: // half word
+ return fmt.Sprintf("ldh [x + %d]", a.Off)
+ case 4: // word
+ return fmt.Sprintf("ld [x + %d]", a.Off)
+ default:
+ return fmt.Sprintf("unknown instruction: %#v", a)
+ }
+}
+
+// LoadMemShift multiplies the first 4 bits of the byte at packet[Off]
+// by 4 and stores the result in register X.
+//
+// This instruction is mainly useful to load into X the length of an
+// IPv4 packet header in a single instruction, rather than have to do
+// the arithmetic on the header's first byte by hand.
+type LoadMemShift struct {
+ Off uint32
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a LoadMemShift) Assemble() (RawInstruction, error) {
+ return assembleLoad(RegX, 1, opAddrModeMemShift, a.Off)
+}
+
+// String returns the instruction in assembler notation.
+func (a LoadMemShift) String() string {
+ return fmt.Sprintf("ldx 4*([%d]&0xf)", a.Off)
+}
+
+// LoadExtension invokes a linux-specific extension and stores the
+// result in register A.
+type LoadExtension struct {
+ Num Extension
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a LoadExtension) Assemble() (RawInstruction, error) {
+ if a.Num == ExtLen {
+ return assembleLoad(RegA, 4, opAddrModePacketLen, 0)
+ }
+ return assembleLoad(RegA, 4, opAddrModeAbsolute, uint32(extOffset+a.Num))
+}
+
+// String returns the instruction in assembler notation.
+func (a LoadExtension) String() string {
+ switch a.Num {
+ case ExtLen:
+ return "ld #len"
+ case ExtProto:
+ return "ld #proto"
+ case ExtType:
+ return "ld #type"
+ case ExtPayloadOffset:
+ return "ld #poff"
+ case ExtInterfaceIndex:
+ return "ld #ifidx"
+ case ExtNetlinkAttr:
+ return "ld #nla"
+ case ExtNetlinkAttrNested:
+ return "ld #nlan"
+ case ExtMark:
+ return "ld #mark"
+ case ExtQueue:
+ return "ld #queue"
+ case ExtLinkLayerType:
+ return "ld #hatype"
+ case ExtRXHash:
+ return "ld #rxhash"
+ case ExtCPUID:
+ return "ld #cpu"
+ case ExtVLANTag:
+ return "ld #vlan_tci"
+ case ExtVLANTagPresent:
+ return "ld #vlan_avail"
+ case ExtVLANProto:
+ return "ld #vlan_tpid"
+ case ExtRand:
+ return "ld #rand"
+ default:
+ return fmt.Sprintf("unknown instruction: %#v", a)
+ }
+}
+
+// StoreScratch stores register Src into scratch[N].
+type StoreScratch struct {
+ Src Register
+ N int // 0-15
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a StoreScratch) Assemble() (RawInstruction, error) {
+ if a.N < 0 || a.N > 15 {
+ return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
+ }
+ var op uint16
+ switch a.Src {
+ case RegA:
+ op = opClsStoreA
+ case RegX:
+ op = opClsStoreX
+ default:
+ return RawInstruction{}, fmt.Errorf("invalid source register %v", a.Src)
+ }
+
+ return RawInstruction{
+ Op: op,
+ K: uint32(a.N),
+ }, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a StoreScratch) String() string {
+ switch a.Src {
+ case RegA:
+ return fmt.Sprintf("st M[%d]", a.N)
+ case RegX:
+ return fmt.Sprintf("stx M[%d]", a.N)
+ default:
+ return fmt.Sprintf("unknown instruction: %#v", a)
+ }
+}
+
+// ALUOpConstant executes A = A Val.
+type ALUOpConstant struct {
+ Op ALUOp
+ Val uint32
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a ALUOpConstant) Assemble() (RawInstruction, error) {
+ return RawInstruction{
+ Op: opClsALU | opALUSrcConstant | uint16(a.Op),
+ K: a.Val,
+ }, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a ALUOpConstant) String() string {
+ switch a.Op {
+ case ALUOpAdd:
+ return fmt.Sprintf("add #%d", a.Val)
+ case ALUOpSub:
+ return fmt.Sprintf("sub #%d", a.Val)
+ case ALUOpMul:
+ return fmt.Sprintf("mul #%d", a.Val)
+ case ALUOpDiv:
+ return fmt.Sprintf("div #%d", a.Val)
+ case ALUOpMod:
+ return fmt.Sprintf("mod #%d", a.Val)
+ case ALUOpAnd:
+ return fmt.Sprintf("and #%d", a.Val)
+ case ALUOpOr:
+ return fmt.Sprintf("or #%d", a.Val)
+ case ALUOpXor:
+ return fmt.Sprintf("xor #%d", a.Val)
+ case ALUOpShiftLeft:
+ return fmt.Sprintf("lsh #%d", a.Val)
+ case ALUOpShiftRight:
+ return fmt.Sprintf("rsh #%d", a.Val)
+ default:
+ return fmt.Sprintf("unknown instruction: %#v", a)
+ }
+}
+
+// ALUOpX executes A = A X
+type ALUOpX struct {
+ Op ALUOp
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a ALUOpX) Assemble() (RawInstruction, error) {
+ return RawInstruction{
+ Op: opClsALU | opALUSrcX | uint16(a.Op),
+ }, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a ALUOpX) String() string {
+ switch a.Op {
+ case ALUOpAdd:
+ return "add x"
+ case ALUOpSub:
+ return "sub x"
+ case ALUOpMul:
+ return "mul x"
+ case ALUOpDiv:
+ return "div x"
+ case ALUOpMod:
+ return "mod x"
+ case ALUOpAnd:
+ return "and x"
+ case ALUOpOr:
+ return "or x"
+ case ALUOpXor:
+ return "xor x"
+ case ALUOpShiftLeft:
+ return "lsh x"
+ case ALUOpShiftRight:
+ return "rsh x"
+ default:
+ return fmt.Sprintf("unknown instruction: %#v", a)
+ }
+}
+
+// NegateA executes A = -A.
+type NegateA struct{}
+
+// Assemble implements the Instruction Assemble method.
+func (a NegateA) Assemble() (RawInstruction, error) {
+ return RawInstruction{
+ Op: opClsALU | uint16(aluOpNeg),
+ }, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a NegateA) String() string {
+ return fmt.Sprintf("neg")
+}
+
+// Jump skips the following Skip instructions in the program.
+type Jump struct {
+ Skip uint32
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a Jump) Assemble() (RawInstruction, error) {
+ return RawInstruction{
+ Op: opClsJump | opJumpAlways,
+ K: a.Skip,
+ }, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a Jump) String() string {
+ return fmt.Sprintf("ja %d", a.Skip)
+}
+
+// JumpIf skips the following Skip instructions in the program if A
+// Val is true.
+type JumpIf struct {
+ Cond JumpTest
+ Val uint32
+ SkipTrue uint8
+ SkipFalse uint8
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a JumpIf) Assemble() (RawInstruction, error) {
+ var (
+ cond uint16
+ flip bool
+ )
+ switch a.Cond {
+ case JumpEqual:
+ cond = opJumpEqual
+ case JumpNotEqual:
+ cond, flip = opJumpEqual, true
+ case JumpGreaterThan:
+ cond = opJumpGT
+ case JumpLessThan:
+ cond, flip = opJumpGE, true
+ case JumpGreaterOrEqual:
+ cond = opJumpGE
+ case JumpLessOrEqual:
+ cond, flip = opJumpGT, true
+ case JumpBitsSet:
+ cond = opJumpSet
+ case JumpBitsNotSet:
+ cond, flip = opJumpSet, true
+ default:
+ return RawInstruction{}, fmt.Errorf("unknown JumpTest %v", a.Cond)
+ }
+ jt, jf := a.SkipTrue, a.SkipFalse
+ if flip {
+ jt, jf = jf, jt
+ }
+ return RawInstruction{
+ Op: opClsJump | cond,
+ Jt: jt,
+ Jf: jf,
+ K: a.Val,
+ }, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a JumpIf) String() string {
+ switch a.Cond {
+ // K == A
+ case JumpEqual:
+ return conditionalJump(a, "jeq", "jneq")
+ // K != A
+ case JumpNotEqual:
+ return fmt.Sprintf("jneq #%d,%d", a.Val, a.SkipTrue)
+ // K > A
+ case JumpGreaterThan:
+ return conditionalJump(a, "jgt", "jle")
+ // K < A
+ case JumpLessThan:
+ return fmt.Sprintf("jlt #%d,%d", a.Val, a.SkipTrue)
+ // K >= A
+ case JumpGreaterOrEqual:
+ return conditionalJump(a, "jge", "jlt")
+ // K <= A
+ case JumpLessOrEqual:
+ return fmt.Sprintf("jle #%d,%d", a.Val, a.SkipTrue)
+ // K & A != 0
+ case JumpBitsSet:
+ if a.SkipFalse > 0 {
+ return fmt.Sprintf("jset #%d,%d,%d", a.Val, a.SkipTrue, a.SkipFalse)
+ }
+ return fmt.Sprintf("jset #%d,%d", a.Val, a.SkipTrue)
+ // K & A == 0, there is no assembler instruction for JumpBitNotSet, use JumpBitSet and invert skips
+ case JumpBitsNotSet:
+ return JumpIf{Cond: JumpBitsSet, SkipTrue: a.SkipFalse, SkipFalse: a.SkipTrue, Val: a.Val}.String()
+ default:
+ return fmt.Sprintf("unknown instruction: %#v", a)
+ }
+}
+
+func conditionalJump(inst JumpIf, positiveJump, negativeJump string) string {
+ if inst.SkipTrue > 0 {
+ if inst.SkipFalse > 0 {
+ return fmt.Sprintf("%s #%d,%d,%d", positiveJump, inst.Val, inst.SkipTrue, inst.SkipFalse)
+ }
+ return fmt.Sprintf("%s #%d,%d", positiveJump, inst.Val, inst.SkipTrue)
+ }
+ return fmt.Sprintf("%s #%d,%d", negativeJump, inst.Val, inst.SkipFalse)
+}
+
+// RetA exits the BPF program, returning the value of register A.
+type RetA struct{}
+
+// Assemble implements the Instruction Assemble method.
+func (a RetA) Assemble() (RawInstruction, error) {
+ return RawInstruction{
+ Op: opClsReturn | opRetSrcA,
+ }, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a RetA) String() string {
+ return fmt.Sprintf("ret a")
+}
+
+// RetConstant exits the BPF program, returning a constant value.
+type RetConstant struct {
+ Val uint32
+}
+
+// Assemble implements the Instruction Assemble method.
+func (a RetConstant) Assemble() (RawInstruction, error) {
+ return RawInstruction{
+ Op: opClsReturn | opRetSrcConstant,
+ K: a.Val,
+ }, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a RetConstant) String() string {
+ return fmt.Sprintf("ret #%d", a.Val)
+}
+
+// TXA copies the value of register X to register A.
+type TXA struct{}
+
+// Assemble implements the Instruction Assemble method.
+func (a TXA) Assemble() (RawInstruction, error) {
+ return RawInstruction{
+ Op: opClsMisc | opMiscTXA,
+ }, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a TXA) String() string {
+ return fmt.Sprintf("txa")
+}
+
+// TAX copies the value of register A to register X.
+type TAX struct{}
+
+// Assemble implements the Instruction Assemble method.
+func (a TAX) Assemble() (RawInstruction, error) {
+ return RawInstruction{
+ Op: opClsMisc | opMiscTAX,
+ }, nil
+}
+
+// String returns the instruction in assembler notation.
+func (a TAX) String() string {
+ return fmt.Sprintf("tax")
+}
+
+func assembleLoad(dst Register, loadSize int, mode uint16, k uint32) (RawInstruction, error) {
+ var (
+ cls uint16
+ sz uint16
+ )
+ switch dst {
+ case RegA:
+ cls = opClsLoadA
+ case RegX:
+ cls = opClsLoadX
+ default:
+ return RawInstruction{}, fmt.Errorf("invalid target register %v", dst)
+ }
+ switch loadSize {
+ case 1:
+ sz = opLoadWidth1
+ case 2:
+ sz = opLoadWidth2
+ case 4:
+ sz = opLoadWidth4
+ default:
+ return RawInstruction{}, fmt.Errorf("invalid load byte length %d", sz)
+ }
+ return RawInstruction{
+ Op: cls | sz | mode,
+ K: k,
+ }, nil
+}
diff --git a/vendor/golang.org/x/net/bpf/instructions_test.go b/vendor/golang.org/x/net/bpf/instructions_test.go
new file mode 100644
index 0000000..dde474a
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/instructions_test.go
@@ -0,0 +1,525 @@
+// Copyright 2016 The Go 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 bpf
+
+import (
+ "fmt"
+ "io/ioutil"
+ "reflect"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+// This is a direct translation of the program in
+// testdata/all_instructions.txt.
+var allInstructions = []Instruction{
+ LoadConstant{Dst: RegA, Val: 42},
+ LoadConstant{Dst: RegX, Val: 42},
+
+ LoadScratch{Dst: RegA, N: 3},
+ LoadScratch{Dst: RegX, N: 3},
+
+ LoadAbsolute{Off: 42, Size: 1},
+ LoadAbsolute{Off: 42, Size: 2},
+ LoadAbsolute{Off: 42, Size: 4},
+
+ LoadIndirect{Off: 42, Size: 1},
+ LoadIndirect{Off: 42, Size: 2},
+ LoadIndirect{Off: 42, Size: 4},
+
+ LoadMemShift{Off: 42},
+
+ LoadExtension{Num: ExtLen},
+ LoadExtension{Num: ExtProto},
+ LoadExtension{Num: ExtType},
+ LoadExtension{Num: ExtRand},
+
+ StoreScratch{Src: RegA, N: 3},
+ StoreScratch{Src: RegX, N: 3},
+
+ ALUOpConstant{Op: ALUOpAdd, Val: 42},
+ ALUOpConstant{Op: ALUOpSub, Val: 42},
+ ALUOpConstant{Op: ALUOpMul, Val: 42},
+ ALUOpConstant{Op: ALUOpDiv, Val: 42},
+ ALUOpConstant{Op: ALUOpOr, Val: 42},
+ ALUOpConstant{Op: ALUOpAnd, Val: 42},
+ ALUOpConstant{Op: ALUOpShiftLeft, Val: 42},
+ ALUOpConstant{Op: ALUOpShiftRight, Val: 42},
+ ALUOpConstant{Op: ALUOpMod, Val: 42},
+ ALUOpConstant{Op: ALUOpXor, Val: 42},
+
+ ALUOpX{Op: ALUOpAdd},
+ ALUOpX{Op: ALUOpSub},
+ ALUOpX{Op: ALUOpMul},
+ ALUOpX{Op: ALUOpDiv},
+ ALUOpX{Op: ALUOpOr},
+ ALUOpX{Op: ALUOpAnd},
+ ALUOpX{Op: ALUOpShiftLeft},
+ ALUOpX{Op: ALUOpShiftRight},
+ ALUOpX{Op: ALUOpMod},
+ ALUOpX{Op: ALUOpXor},
+
+ NegateA{},
+
+ Jump{Skip: 10},
+ JumpIf{Cond: JumpEqual, Val: 42, SkipTrue: 8, SkipFalse: 9},
+ JumpIf{Cond: JumpNotEqual, Val: 42, SkipTrue: 8},
+ JumpIf{Cond: JumpLessThan, Val: 42, SkipTrue: 7},
+ JumpIf{Cond: JumpLessOrEqual, Val: 42, SkipTrue: 6},
+ JumpIf{Cond: JumpGreaterThan, Val: 42, SkipTrue: 4, SkipFalse: 5},
+ JumpIf{Cond: JumpGreaterOrEqual, Val: 42, SkipTrue: 3, SkipFalse: 4},
+ JumpIf{Cond: JumpBitsSet, Val: 42, SkipTrue: 2, SkipFalse: 3},
+
+ TAX{},
+ TXA{},
+
+ RetA{},
+ RetConstant{Val: 42},
+}
+var allInstructionsExpected = "testdata/all_instructions.bpf"
+
+// Check that we produce the same output as the canonical bpf_asm
+// linux kernel tool.
+func TestInterop(t *testing.T) {
+ out, err := Assemble(allInstructions)
+ if err != nil {
+ t.Fatalf("assembly of allInstructions program failed: %s", err)
+ }
+ t.Logf("Assembled program is %d instructions long", len(out))
+
+ bs, err := ioutil.ReadFile(allInstructionsExpected)
+ if err != nil {
+ t.Fatalf("reading %s: %s", allInstructionsExpected, err)
+ }
+ // First statement is the number of statements, last statement is
+ // empty. We just ignore both and rely on slice length.
+ stmts := strings.Split(string(bs), ",")
+ if len(stmts)-2 != len(out) {
+ t.Fatalf("test program lengths don't match: %s has %d, Go implementation has %d", allInstructionsExpected, len(stmts)-2, len(allInstructions))
+ }
+
+ for i, stmt := range stmts[1 : len(stmts)-2] {
+ nums := strings.Split(stmt, " ")
+ if len(nums) != 4 {
+ t.Fatalf("malformed instruction %d in %s: %s", i+1, allInstructionsExpected, stmt)
+ }
+
+ actual := out[i]
+
+ op, err := strconv.ParseUint(nums[0], 10, 16)
+ if err != nil {
+ t.Fatalf("malformed opcode %s in instruction %d of %s", nums[0], i+1, allInstructionsExpected)
+ }
+ if actual.Op != uint16(op) {
+ t.Errorf("opcode mismatch on instruction %d (%#v): got 0x%02x, want 0x%02x", i+1, allInstructions[i], actual.Op, op)
+ }
+
+ jt, err := strconv.ParseUint(nums[1], 10, 8)
+ if err != nil {
+ t.Fatalf("malformed jt offset %s in instruction %d of %s", nums[1], i+1, allInstructionsExpected)
+ }
+ if actual.Jt != uint8(jt) {
+ t.Errorf("jt mismatch on instruction %d (%#v): got %d, want %d", i+1, allInstructions[i], actual.Jt, jt)
+ }
+
+ jf, err := strconv.ParseUint(nums[2], 10, 8)
+ if err != nil {
+ t.Fatalf("malformed jf offset %s in instruction %d of %s", nums[2], i+1, allInstructionsExpected)
+ }
+ if actual.Jf != uint8(jf) {
+ t.Errorf("jf mismatch on instruction %d (%#v): got %d, want %d", i+1, allInstructions[i], actual.Jf, jf)
+ }
+
+ k, err := strconv.ParseUint(nums[3], 10, 32)
+ if err != nil {
+ t.Fatalf("malformed constant %s in instruction %d of %s", nums[3], i+1, allInstructionsExpected)
+ }
+ if actual.K != uint32(k) {
+ t.Errorf("constant mismatch on instruction %d (%#v): got %d, want %d", i+1, allInstructions[i], actual.K, k)
+ }
+ }
+}
+
+// Check that assembly and disassembly match each other.
+func TestAsmDisasm(t *testing.T) {
+ prog1, err := Assemble(allInstructions)
+ if err != nil {
+ t.Fatalf("assembly of allInstructions program failed: %s", err)
+ }
+ t.Logf("Assembled program is %d instructions long", len(prog1))
+
+ got, allDecoded := Disassemble(prog1)
+ if !allDecoded {
+ t.Errorf("Disassemble(Assemble(allInstructions)) produced unrecognized instructions:")
+ for i, inst := range got {
+ if r, ok := inst.(RawInstruction); ok {
+ t.Logf(" insn %d, %#v --> %#v", i+1, allInstructions[i], r)
+ }
+ }
+ }
+
+ if len(allInstructions) != len(got) {
+ t.Fatalf("disassembly changed program size: %d insns before, %d insns after", len(allInstructions), len(got))
+ }
+ if !reflect.DeepEqual(allInstructions, got) {
+ t.Errorf("program mutated by disassembly:")
+ for i := range got {
+ if !reflect.DeepEqual(allInstructions[i], got[i]) {
+ t.Logf(" insn %d, s: %#v, p1: %#v, got: %#v", i+1, allInstructions[i], prog1[i], got[i])
+ }
+ }
+ }
+}
+
+type InvalidInstruction struct{}
+
+func (a InvalidInstruction) Assemble() (RawInstruction, error) {
+ return RawInstruction{}, fmt.Errorf("Invalid Instruction")
+}
+
+func (a InvalidInstruction) String() string {
+ return fmt.Sprintf("unknown instruction: %#v", a)
+}
+
+func TestString(t *testing.T) {
+ testCases := []struct {
+ instruction Instruction
+ assembler string
+ }{
+ {
+ instruction: LoadConstant{Dst: RegA, Val: 42},
+ assembler: "ld #42",
+ },
+ {
+ instruction: LoadConstant{Dst: RegX, Val: 42},
+ assembler: "ldx #42",
+ },
+ {
+ instruction: LoadConstant{Dst: 0xffff, Val: 42},
+ assembler: "unknown instruction: bpf.LoadConstant{Dst:0xffff, Val:0x2a}",
+ },
+ {
+ instruction: LoadScratch{Dst: RegA, N: 3},
+ assembler: "ld M[3]",
+ },
+ {
+ instruction: LoadScratch{Dst: RegX, N: 3},
+ assembler: "ldx M[3]",
+ },
+ {
+ instruction: LoadScratch{Dst: 0xffff, N: 3},
+ assembler: "unknown instruction: bpf.LoadScratch{Dst:0xffff, N:3}",
+ },
+ {
+ instruction: LoadAbsolute{Off: 42, Size: 1},
+ assembler: "ldb [42]",
+ },
+ {
+ instruction: LoadAbsolute{Off: 42, Size: 2},
+ assembler: "ldh [42]",
+ },
+ {
+ instruction: LoadAbsolute{Off: 42, Size: 4},
+ assembler: "ld [42]",
+ },
+ {
+ instruction: LoadAbsolute{Off: 42, Size: -1},
+ assembler: "unknown instruction: bpf.LoadAbsolute{Off:0x2a, Size:-1}",
+ },
+ {
+ instruction: LoadIndirect{Off: 42, Size: 1},
+ assembler: "ldb [x + 42]",
+ },
+ {
+ instruction: LoadIndirect{Off: 42, Size: 2},
+ assembler: "ldh [x + 42]",
+ },
+ {
+ instruction: LoadIndirect{Off: 42, Size: 4},
+ assembler: "ld [x + 42]",
+ },
+ {
+ instruction: LoadIndirect{Off: 42, Size: -1},
+ assembler: "unknown instruction: bpf.LoadIndirect{Off:0x2a, Size:-1}",
+ },
+ {
+ instruction: LoadMemShift{Off: 42},
+ assembler: "ldx 4*([42]&0xf)",
+ },
+ {
+ instruction: LoadExtension{Num: ExtLen},
+ assembler: "ld #len",
+ },
+ {
+ instruction: LoadExtension{Num: ExtProto},
+ assembler: "ld #proto",
+ },
+ {
+ instruction: LoadExtension{Num: ExtType},
+ assembler: "ld #type",
+ },
+ {
+ instruction: LoadExtension{Num: ExtPayloadOffset},
+ assembler: "ld #poff",
+ },
+ {
+ instruction: LoadExtension{Num: ExtInterfaceIndex},
+ assembler: "ld #ifidx",
+ },
+ {
+ instruction: LoadExtension{Num: ExtNetlinkAttr},
+ assembler: "ld #nla",
+ },
+ {
+ instruction: LoadExtension{Num: ExtNetlinkAttrNested},
+ assembler: "ld #nlan",
+ },
+ {
+ instruction: LoadExtension{Num: ExtMark},
+ assembler: "ld #mark",
+ },
+ {
+ instruction: LoadExtension{Num: ExtQueue},
+ assembler: "ld #queue",
+ },
+ {
+ instruction: LoadExtension{Num: ExtLinkLayerType},
+ assembler: "ld #hatype",
+ },
+ {
+ instruction: LoadExtension{Num: ExtRXHash},
+ assembler: "ld #rxhash",
+ },
+ {
+ instruction: LoadExtension{Num: ExtCPUID},
+ assembler: "ld #cpu",
+ },
+ {
+ instruction: LoadExtension{Num: ExtVLANTag},
+ assembler: "ld #vlan_tci",
+ },
+ {
+ instruction: LoadExtension{Num: ExtVLANTagPresent},
+ assembler: "ld #vlan_avail",
+ },
+ {
+ instruction: LoadExtension{Num: ExtVLANProto},
+ assembler: "ld #vlan_tpid",
+ },
+ {
+ instruction: LoadExtension{Num: ExtRand},
+ assembler: "ld #rand",
+ },
+ {
+ instruction: LoadAbsolute{Off: 0xfffff038, Size: 4},
+ assembler: "ld #rand",
+ },
+ {
+ instruction: LoadExtension{Num: 0xfff},
+ assembler: "unknown instruction: bpf.LoadExtension{Num:4095}",
+ },
+ {
+ instruction: StoreScratch{Src: RegA, N: 3},
+ assembler: "st M[3]",
+ },
+ {
+ instruction: StoreScratch{Src: RegX, N: 3},
+ assembler: "stx M[3]",
+ },
+ {
+ instruction: StoreScratch{Src: 0xffff, N: 3},
+ assembler: "unknown instruction: bpf.StoreScratch{Src:0xffff, N:3}",
+ },
+ {
+ instruction: ALUOpConstant{Op: ALUOpAdd, Val: 42},
+ assembler: "add #42",
+ },
+ {
+ instruction: ALUOpConstant{Op: ALUOpSub, Val: 42},
+ assembler: "sub #42",
+ },
+ {
+ instruction: ALUOpConstant{Op: ALUOpMul, Val: 42},
+ assembler: "mul #42",
+ },
+ {
+ instruction: ALUOpConstant{Op: ALUOpDiv, Val: 42},
+ assembler: "div #42",
+ },
+ {
+ instruction: ALUOpConstant{Op: ALUOpOr, Val: 42},
+ assembler: "or #42",
+ },
+ {
+ instruction: ALUOpConstant{Op: ALUOpAnd, Val: 42},
+ assembler: "and #42",
+ },
+ {
+ instruction: ALUOpConstant{Op: ALUOpShiftLeft, Val: 42},
+ assembler: "lsh #42",
+ },
+ {
+ instruction: ALUOpConstant{Op: ALUOpShiftRight, Val: 42},
+ assembler: "rsh #42",
+ },
+ {
+ instruction: ALUOpConstant{Op: ALUOpMod, Val: 42},
+ assembler: "mod #42",
+ },
+ {
+ instruction: ALUOpConstant{Op: ALUOpXor, Val: 42},
+ assembler: "xor #42",
+ },
+ {
+ instruction: ALUOpConstant{Op: 0xffff, Val: 42},
+ assembler: "unknown instruction: bpf.ALUOpConstant{Op:0xffff, Val:0x2a}",
+ },
+ {
+ instruction: ALUOpX{Op: ALUOpAdd},
+ assembler: "add x",
+ },
+ {
+ instruction: ALUOpX{Op: ALUOpSub},
+ assembler: "sub x",
+ },
+ {
+ instruction: ALUOpX{Op: ALUOpMul},
+ assembler: "mul x",
+ },
+ {
+ instruction: ALUOpX{Op: ALUOpDiv},
+ assembler: "div x",
+ },
+ {
+ instruction: ALUOpX{Op: ALUOpOr},
+ assembler: "or x",
+ },
+ {
+ instruction: ALUOpX{Op: ALUOpAnd},
+ assembler: "and x",
+ },
+ {
+ instruction: ALUOpX{Op: ALUOpShiftLeft},
+ assembler: "lsh x",
+ },
+ {
+ instruction: ALUOpX{Op: ALUOpShiftRight},
+ assembler: "rsh x",
+ },
+ {
+ instruction: ALUOpX{Op: ALUOpMod},
+ assembler: "mod x",
+ },
+ {
+ instruction: ALUOpX{Op: ALUOpXor},
+ assembler: "xor x",
+ },
+ {
+ instruction: ALUOpX{Op: 0xffff},
+ assembler: "unknown instruction: bpf.ALUOpX{Op:0xffff}",
+ },
+ {
+ instruction: NegateA{},
+ assembler: "neg",
+ },
+ {
+ instruction: Jump{Skip: 10},
+ assembler: "ja 10",
+ },
+ {
+ instruction: JumpIf{Cond: JumpEqual, Val: 42, SkipTrue: 8, SkipFalse: 9},
+ assembler: "jeq #42,8,9",
+ },
+ {
+ instruction: JumpIf{Cond: JumpEqual, Val: 42, SkipTrue: 8},
+ assembler: "jeq #42,8",
+ },
+ {
+ instruction: JumpIf{Cond: JumpEqual, Val: 42, SkipFalse: 8},
+ assembler: "jneq #42,8",
+ },
+ {
+ instruction: JumpIf{Cond: JumpNotEqual, Val: 42, SkipTrue: 8},
+ assembler: "jneq #42,8",
+ },
+ {
+ instruction: JumpIf{Cond: JumpLessThan, Val: 42, SkipTrue: 7},
+ assembler: "jlt #42,7",
+ },
+ {
+ instruction: JumpIf{Cond: JumpLessOrEqual, Val: 42, SkipTrue: 6},
+ assembler: "jle #42,6",
+ },
+ {
+ instruction: JumpIf{Cond: JumpGreaterThan, Val: 42, SkipTrue: 4, SkipFalse: 5},
+ assembler: "jgt #42,4,5",
+ },
+ {
+ instruction: JumpIf{Cond: JumpGreaterThan, Val: 42, SkipTrue: 4},
+ assembler: "jgt #42,4",
+ },
+ {
+ instruction: JumpIf{Cond: JumpGreaterOrEqual, Val: 42, SkipTrue: 3, SkipFalse: 4},
+ assembler: "jge #42,3,4",
+ },
+ {
+ instruction: JumpIf{Cond: JumpGreaterOrEqual, Val: 42, SkipTrue: 3},
+ assembler: "jge #42,3",
+ },
+ {
+ instruction: JumpIf{Cond: JumpBitsSet, Val: 42, SkipTrue: 2, SkipFalse: 3},
+ assembler: "jset #42,2,3",
+ },
+ {
+ instruction: JumpIf{Cond: JumpBitsSet, Val: 42, SkipTrue: 2},
+ assembler: "jset #42,2",
+ },
+ {
+ instruction: JumpIf{Cond: JumpBitsNotSet, Val: 42, SkipTrue: 2, SkipFalse: 3},
+ assembler: "jset #42,3,2",
+ },
+ {
+ instruction: JumpIf{Cond: JumpBitsNotSet, Val: 42, SkipTrue: 2},
+ assembler: "jset #42,0,2",
+ },
+ {
+ instruction: JumpIf{Cond: 0xffff, Val: 42, SkipTrue: 1, SkipFalse: 2},
+ assembler: "unknown instruction: bpf.JumpIf{Cond:0xffff, Val:0x2a, SkipTrue:0x1, SkipFalse:0x2}",
+ },
+ {
+ instruction: TAX{},
+ assembler: "tax",
+ },
+ {
+ instruction: TXA{},
+ assembler: "txa",
+ },
+ {
+ instruction: RetA{},
+ assembler: "ret a",
+ },
+ {
+ instruction: RetConstant{Val: 42},
+ assembler: "ret #42",
+ },
+ // Invalid instruction
+ {
+ instruction: InvalidInstruction{},
+ assembler: "unknown instruction: bpf.InvalidInstruction{}",
+ },
+ }
+
+ for _, testCase := range testCases {
+ if input, ok := testCase.instruction.(fmt.Stringer); ok {
+ got := input.String()
+ if got != testCase.assembler {
+ t.Errorf("String did not return expected assembler notation, expected: %s, got: %s", testCase.assembler, got)
+ }
+ } else {
+ t.Errorf("Instruction %#v is not a fmt.Stringer", testCase.instruction)
+ }
+ }
+}
diff --git a/vendor/golang.org/x/net/bpf/setter.go b/vendor/golang.org/x/net/bpf/setter.go
new file mode 100644
index 0000000..43e35f0
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/setter.go
@@ -0,0 +1,10 @@
+// Copyright 2017 The Go 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 bpf
+
+// A Setter is a type which can attach a compiled BPF filter to itself.
+type Setter interface {
+ SetBPF(filter []RawInstruction) error
+}
diff --git a/vendor/golang.org/x/net/bpf/testdata/all_instructions.bpf b/vendor/golang.org/x/net/bpf/testdata/all_instructions.bpf
new file mode 100644
index 0000000..f871440
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/testdata/all_instructions.bpf
@@ -0,0 +1 @@
+50,0 0 0 42,1 0 0 42,96 0 0 3,97 0 0 3,48 0 0 42,40 0 0 42,32 0 0 42,80 0 0 42,72 0 0 42,64 0 0 42,177 0 0 42,128 0 0 0,32 0 0 4294963200,32 0 0 4294963204,32 0 0 4294963256,2 0 0 3,3 0 0 3,4 0 0 42,20 0 0 42,36 0 0 42,52 0 0 42,68 0 0 42,84 0 0 42,100 0 0 42,116 0 0 42,148 0 0 42,164 0 0 42,12 0 0 0,28 0 0 0,44 0 0 0,60 0 0 0,76 0 0 0,92 0 0 0,108 0 0 0,124 0 0 0,156 0 0 0,172 0 0 0,132 0 0 0,5 0 0 10,21 8 9 42,21 0 8 42,53 0 7 42,37 0 6 42,37 4 5 42,53 3 4 42,69 2 3 42,7 0 0 0,135 0 0 0,22 0 0 0,6 0 0 0,
diff --git a/vendor/golang.org/x/net/bpf/testdata/all_instructions.txt b/vendor/golang.org/x/net/bpf/testdata/all_instructions.txt
new file mode 100644
index 0000000..3045501
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/testdata/all_instructions.txt
@@ -0,0 +1,79 @@
+# This filter is compiled to all_instructions.bpf by the `bpf_asm`
+# tool, which can be found in the linux kernel source tree under
+# tools/net.
+
+# Load immediate
+ld #42
+ldx #42
+
+# Load scratch
+ld M[3]
+ldx M[3]
+
+# Load absolute
+ldb [42]
+ldh [42]
+ld [42]
+
+# Load indirect
+ldb [x + 42]
+ldh [x + 42]
+ld [x + 42]
+
+# Load IPv4 header length
+ldx 4*([42]&0xf)
+
+# Run extension function
+ld #len
+ld #proto
+ld #type
+ld #rand
+
+# Store scratch
+st M[3]
+stx M[3]
+
+# A constant
+add #42
+sub #42
+mul #42
+div #42
+or #42
+and #42
+lsh #42
+rsh #42
+mod #42
+xor #42
+
+# A X
+add x
+sub x
+mul x
+div x
+or x
+and x
+lsh x
+rsh x
+mod x
+xor x
+
+# !A
+neg
+
+# Jumps
+ja end
+jeq #42,prev,end
+jne #42,end
+jlt #42,end
+jle #42,end
+jgt #42,prev,end
+jge #42,prev,end
+jset #42,prev,end
+
+# Register transfers
+tax
+txa
+
+# Returns
+prev: ret a
+end: ret #42
diff --git a/vendor/golang.org/x/net/bpf/vm.go b/vendor/golang.org/x/net/bpf/vm.go
new file mode 100644
index 0000000..4c656f1
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/vm.go
@@ -0,0 +1,140 @@
+// Copyright 2016 The Go 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 bpf
+
+import (
+ "errors"
+ "fmt"
+)
+
+// A VM is an emulated BPF virtual machine.
+type VM struct {
+ filter []Instruction
+}
+
+// NewVM returns a new VM using the input BPF program.
+func NewVM(filter []Instruction) (*VM, error) {
+ if len(filter) == 0 {
+ return nil, errors.New("one or more Instructions must be specified")
+ }
+
+ for i, ins := range filter {
+ check := len(filter) - (i + 1)
+ switch ins := ins.(type) {
+ // Check for out-of-bounds jumps in instructions
+ case Jump:
+ if check <= int(ins.Skip) {
+ return nil, fmt.Errorf("cannot jump %d instructions; jumping past program bounds", ins.Skip)
+ }
+ case JumpIf:
+ if check <= int(ins.SkipTrue) {
+ return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
+ }
+ if check <= int(ins.SkipFalse) {
+ return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
+ }
+ // Check for division or modulus by zero
+ case ALUOpConstant:
+ if ins.Val != 0 {
+ break
+ }
+
+ switch ins.Op {
+ case ALUOpDiv, ALUOpMod:
+ return nil, errors.New("cannot divide by zero using ALUOpConstant")
+ }
+ // Check for unknown extensions
+ case LoadExtension:
+ switch ins.Num {
+ case ExtLen:
+ default:
+ return nil, fmt.Errorf("extension %d not implemented", ins.Num)
+ }
+ }
+ }
+
+ // Make sure last instruction is a return instruction
+ switch filter[len(filter)-1].(type) {
+ case RetA, RetConstant:
+ default:
+ return nil, errors.New("BPF program must end with RetA or RetConstant")
+ }
+
+ // Though our VM works using disassembled instructions, we
+ // attempt to assemble the input filter anyway to ensure it is compatible
+ // with an operating system VM.
+ _, err := Assemble(filter)
+
+ return &VM{
+ filter: filter,
+ }, err
+}
+
+// Run runs the VM's BPF program against the input bytes.
+// Run returns the number of bytes accepted by the BPF program, and any errors
+// which occurred while processing the program.
+func (v *VM) Run(in []byte) (int, error) {
+ var (
+ // Registers of the virtual machine
+ regA uint32
+ regX uint32
+ regScratch [16]uint32
+
+ // OK is true if the program should continue processing the next
+ // instruction, or false if not, causing the loop to break
+ ok = true
+ )
+
+ // TODO(mdlayher): implement:
+ // - NegateA:
+ // - would require a change from uint32 registers to int32
+ // registers
+
+ // TODO(mdlayher): add interop tests that check signedness of ALU
+ // operations against kernel implementation, and make sure Go
+ // implementation matches behavior
+
+ for i := 0; i < len(v.filter) && ok; i++ {
+ ins := v.filter[i]
+
+ switch ins := ins.(type) {
+ case ALUOpConstant:
+ regA = aluOpConstant(ins, regA)
+ case ALUOpX:
+ regA, ok = aluOpX(ins, regA, regX)
+ case Jump:
+ i += int(ins.Skip)
+ case JumpIf:
+ jump := jumpIf(ins, regA)
+ i += jump
+ case LoadAbsolute:
+ regA, ok = loadAbsolute(ins, in)
+ case LoadConstant:
+ regA, regX = loadConstant(ins, regA, regX)
+ case LoadExtension:
+ regA = loadExtension(ins, in)
+ case LoadIndirect:
+ regA, ok = loadIndirect(ins, in, regX)
+ case LoadMemShift:
+ regX, ok = loadMemShift(ins, in)
+ case LoadScratch:
+ regA, regX = loadScratch(ins, regScratch, regA, regX)
+ case RetA:
+ return int(regA), nil
+ case RetConstant:
+ return int(ins.Val), nil
+ case StoreScratch:
+ regScratch = storeScratch(ins, regScratch, regA, regX)
+ case TAX:
+ regX = regA
+ case TXA:
+ regA = regX
+ default:
+ return 0, fmt.Errorf("unknown Instruction at index %d: %T", i, ins)
+ }
+ }
+
+ return 0, nil
+}
diff --git a/vendor/golang.org/x/net/bpf/vm_aluop_test.go b/vendor/golang.org/x/net/bpf/vm_aluop_test.go
new file mode 100644
index 0000000..1667824
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/vm_aluop_test.go
@@ -0,0 +1,512 @@
+// Copyright 2016 The Go 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 bpf_test
+
+import (
+ "testing"
+
+ "golang.org/x/net/bpf"
+)
+
+func TestVMALUOpAdd(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.ALUOpConstant{
+ Op: bpf.ALUOpAdd,
+ Val: 3,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 8, 2, 3,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 3, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMALUOpSub(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.TAX{},
+ bpf.ALUOpX{
+ Op: bpf.ALUOpSub,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 1, 2, 3,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 0, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMALUOpMul(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.ALUOpConstant{
+ Op: bpf.ALUOpMul,
+ Val: 2,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 6, 2, 3, 4,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 4, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMALUOpDiv(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.ALUOpConstant{
+ Op: bpf.ALUOpDiv,
+ Val: 2,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 20, 2, 3, 4,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 2, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMALUOpDivByZeroALUOpConstant(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.ALUOpConstant{
+ Op: bpf.ALUOpDiv,
+ Val: 0,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "cannot divide by zero using ALUOpConstant" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMALUOpDivByZeroALUOpX(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ // Load byte 0 into X
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.TAX{},
+ // Load byte 1 into A
+ bpf.LoadAbsolute{
+ Off: 9,
+ Size: 1,
+ },
+ // Attempt to perform 1/0
+ bpf.ALUOpX{
+ Op: bpf.ALUOpDiv,
+ },
+ // Return 4 bytes if program does not terminate
+ bpf.LoadConstant{
+ Val: 12,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1, 3, 4,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 0, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMALUOpOr(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 2,
+ },
+ bpf.ALUOpConstant{
+ Op: bpf.ALUOpOr,
+ Val: 0x01,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x10, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0xff,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 9, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMALUOpAnd(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 2,
+ },
+ bpf.ALUOpConstant{
+ Op: bpf.ALUOpAnd,
+ Val: 0x0019,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xaa, 0x09,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 1, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMALUOpShiftLeft(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.ALUOpConstant{
+ Op: bpf.ALUOpShiftLeft,
+ Val: 0x01,
+ },
+ bpf.JumpIf{
+ Cond: bpf.JumpEqual,
+ Val: 0x02,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 9,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x01, 0xaa,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 1, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMALUOpShiftRight(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.ALUOpConstant{
+ Op: bpf.ALUOpShiftRight,
+ Val: 0x01,
+ },
+ bpf.JumpIf{
+ Cond: bpf.JumpEqual,
+ Val: 0x04,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 9,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x08, 0xff, 0xff,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 1, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMALUOpMod(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.ALUOpConstant{
+ Op: bpf.ALUOpMod,
+ Val: 20,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 30, 0, 0,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 2, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMALUOpModByZeroALUOpConstant(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.ALUOpConstant{
+ Op: bpf.ALUOpMod,
+ Val: 0,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "cannot divide by zero using ALUOpConstant" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMALUOpModByZeroALUOpX(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ // Load byte 0 into X
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.TAX{},
+ // Load byte 1 into A
+ bpf.LoadAbsolute{
+ Off: 9,
+ Size: 1,
+ },
+ // Attempt to perform 1%0
+ bpf.ALUOpX{
+ Op: bpf.ALUOpMod,
+ },
+ // Return 4 bytes if program does not terminate
+ bpf.LoadConstant{
+ Val: 12,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1, 3, 4,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 0, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMALUOpXor(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.ALUOpConstant{
+ Op: bpf.ALUOpXor,
+ Val: 0x0a,
+ },
+ bpf.JumpIf{
+ Cond: bpf.JumpEqual,
+ Val: 0x01,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 9,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x0b, 0x00, 0x00, 0x00,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 1, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMALUOpUnknown(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.ALUOpConstant{
+ Op: bpf.ALUOpAdd,
+ Val: 1,
+ },
+ // Verify that an unknown operation is a no-op
+ bpf.ALUOpConstant{
+ Op: 100,
+ },
+ bpf.JumpIf{
+ Cond: bpf.JumpEqual,
+ Val: 0x02,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 9,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 1,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 1, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
diff --git a/vendor/golang.org/x/net/bpf/vm_bpf_test.go b/vendor/golang.org/x/net/bpf/vm_bpf_test.go
new file mode 100644
index 0000000..77fa8fe
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/vm_bpf_test.go
@@ -0,0 +1,192 @@
+// Copyright 2016 The Go 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 bpf_test
+
+import (
+ "net"
+ "runtime"
+ "testing"
+ "time"
+
+ "golang.org/x/net/bpf"
+ "golang.org/x/net/ipv4"
+)
+
+// A virtualMachine is a BPF virtual machine which can process an
+// input packet against a BPF program and render a verdict.
+type virtualMachine interface {
+ Run(in []byte) (int, error)
+}
+
+// canUseOSVM indicates if the OS BPF VM is available on this platform.
+func canUseOSVM() bool {
+ // OS BPF VM can only be used on platforms where x/net/ipv4 supports
+ // attaching a BPF program to a socket.
+ switch runtime.GOOS {
+ case "linux":
+ return true
+ }
+
+ return false
+}
+
+// All BPF tests against both the Go VM and OS VM are assumed to
+// be used with a UDP socket. As a result, the entire contents
+// of a UDP datagram is sent through the BPF program, but only
+// the body after the UDP header will ever be returned in output.
+
+// testVM sets up a Go BPF VM, and if available, a native OS BPF VM
+// for integration testing.
+func testVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func(), error) {
+ goVM, err := bpf.NewVM(filter)
+ if err != nil {
+ // Some tests expect an error, so this error must be returned
+ // instead of fatally exiting the test
+ return nil, nil, err
+ }
+
+ mvm := &multiVirtualMachine{
+ goVM: goVM,
+
+ t: t,
+ }
+
+ // If available, add the OS VM for tests which verify that both the Go
+ // VM and OS VM have exactly the same output for the same input program
+ // and packet.
+ done := func() {}
+ if canUseOSVM() {
+ osVM, osVMDone := testOSVM(t, filter)
+ done = func() { osVMDone() }
+ mvm.osVM = osVM
+ }
+
+ return mvm, done, nil
+}
+
+// udpHeaderLen is the length of a UDP header.
+const udpHeaderLen = 8
+
+// A multiVirtualMachine is a virtualMachine which can call out to both the Go VM
+// and the native OS VM, if the OS VM is available.
+type multiVirtualMachine struct {
+ goVM virtualMachine
+ osVM virtualMachine
+
+ t *testing.T
+}
+
+func (mvm *multiVirtualMachine) Run(in []byte) (int, error) {
+ if len(in) < udpHeaderLen {
+ mvm.t.Fatalf("input must be at least length of UDP header (%d), got: %d",
+ udpHeaderLen, len(in))
+ }
+
+ // All tests have a UDP header as part of input, because the OS VM
+ // packets always will. For the Go VM, this output is trimmed before
+ // being sent back to tests.
+ goOut, goErr := mvm.goVM.Run(in)
+ if goOut >= udpHeaderLen {
+ goOut -= udpHeaderLen
+ }
+
+ // If Go output is larger than the size of the packet, packet filtering
+ // interop tests must trim the output bytes to the length of the packet.
+ // The BPF VM should not do this on its own, as other uses of it do
+ // not trim the output byte count.
+ trim := len(in) - udpHeaderLen
+ if goOut > trim {
+ goOut = trim
+ }
+
+ // When the OS VM is not available, process using the Go VM alone
+ if mvm.osVM == nil {
+ return goOut, goErr
+ }
+
+ // The OS VM will apply its own UDP header, so remove the pseudo header
+ // that the Go VM needs.
+ osOut, err := mvm.osVM.Run(in[udpHeaderLen:])
+ if err != nil {
+ mvm.t.Fatalf("error while running OS VM: %v", err)
+ }
+
+ // Verify both VMs return same number of bytes
+ var mismatch bool
+ if goOut != osOut {
+ mismatch = true
+ mvm.t.Logf("output byte count does not match:\n- go: %v\n- os: %v", goOut, osOut)
+ }
+
+ if mismatch {
+ mvm.t.Fatal("Go BPF and OS BPF packet outputs do not match")
+ }
+
+ return goOut, goErr
+}
+
+// An osVirtualMachine is a virtualMachine which uses the OS's BPF VM for
+// processing BPF programs.
+type osVirtualMachine struct {
+ l net.PacketConn
+ s net.Conn
+}
+
+// testOSVM creates a virtualMachine which uses the OS's BPF VM by injecting
+// packets into a UDP listener with a BPF program attached to it.
+func testOSVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func()) {
+ l, err := net.ListenPacket("udp4", "127.0.0.1:0")
+ if err != nil {
+ t.Fatalf("failed to open OS VM UDP listener: %v", err)
+ }
+
+ prog, err := bpf.Assemble(filter)
+ if err != nil {
+ t.Fatalf("failed to compile BPF program: %v", err)
+ }
+
+ p := ipv4.NewPacketConn(l)
+ if err = p.SetBPF(prog); err != nil {
+ t.Fatalf("failed to attach BPF program to listener: %v", err)
+ }
+
+ s, err := net.Dial("udp4", l.LocalAddr().String())
+ if err != nil {
+ t.Fatalf("failed to dial connection to listener: %v", err)
+ }
+
+ done := func() {
+ _ = s.Close()
+ _ = l.Close()
+ }
+
+ return &osVirtualMachine{
+ l: l,
+ s: s,
+ }, done
+}
+
+// Run sends the input bytes into the OS's BPF VM and returns its verdict.
+func (vm *osVirtualMachine) Run(in []byte) (int, error) {
+ go func() {
+ _, _ = vm.s.Write(in)
+ }()
+
+ vm.l.SetDeadline(time.Now().Add(50 * time.Millisecond))
+
+ var b [512]byte
+ n, _, err := vm.l.ReadFrom(b[:])
+ if err != nil {
+ // A timeout indicates that BPF filtered out the packet, and thus,
+ // no input should be returned.
+ if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
+ return n, nil
+ }
+
+ return n, err
+ }
+
+ return n, nil
+}
diff --git a/vendor/golang.org/x/net/bpf/vm_extension_test.go b/vendor/golang.org/x/net/bpf/vm_extension_test.go
new file mode 100644
index 0000000..7a48c82
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/vm_extension_test.go
@@ -0,0 +1,49 @@
+// Copyright 2016 The Go 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 bpf_test
+
+import (
+ "testing"
+
+ "golang.org/x/net/bpf"
+)
+
+func TestVMLoadExtensionNotImplemented(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.LoadExtension{
+ Num: 100,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "extension 100 not implemented" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMLoadExtensionExtLen(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadExtension{
+ Num: bpf.ExtLen,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1, 2, 3,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 4, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
diff --git a/vendor/golang.org/x/net/bpf/vm_instructions.go b/vendor/golang.org/x/net/bpf/vm_instructions.go
new file mode 100644
index 0000000..516f946
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/vm_instructions.go
@@ -0,0 +1,174 @@
+// Copyright 2016 The Go 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 bpf
+
+import (
+ "encoding/binary"
+ "fmt"
+)
+
+func aluOpConstant(ins ALUOpConstant, regA uint32) uint32 {
+ return aluOpCommon(ins.Op, regA, ins.Val)
+}
+
+func aluOpX(ins ALUOpX, regA uint32, regX uint32) (uint32, bool) {
+ // Guard against division or modulus by zero by terminating
+ // the program, as the OS BPF VM does
+ if regX == 0 {
+ switch ins.Op {
+ case ALUOpDiv, ALUOpMod:
+ return 0, false
+ }
+ }
+
+ return aluOpCommon(ins.Op, regA, regX), true
+}
+
+func aluOpCommon(op ALUOp, regA uint32, value uint32) uint32 {
+ switch op {
+ case ALUOpAdd:
+ return regA + value
+ case ALUOpSub:
+ return regA - value
+ case ALUOpMul:
+ return regA * value
+ case ALUOpDiv:
+ // Division by zero not permitted by NewVM and aluOpX checks
+ return regA / value
+ case ALUOpOr:
+ return regA | value
+ case ALUOpAnd:
+ return regA & value
+ case ALUOpShiftLeft:
+ return regA << value
+ case ALUOpShiftRight:
+ return regA >> value
+ case ALUOpMod:
+ // Modulus by zero not permitted by NewVM and aluOpX checks
+ return regA % value
+ case ALUOpXor:
+ return regA ^ value
+ default:
+ return regA
+ }
+}
+
+func jumpIf(ins JumpIf, value uint32) int {
+ var ok bool
+ inV := uint32(ins.Val)
+
+ switch ins.Cond {
+ case JumpEqual:
+ ok = value == inV
+ case JumpNotEqual:
+ ok = value != inV
+ case JumpGreaterThan:
+ ok = value > inV
+ case JumpLessThan:
+ ok = value < inV
+ case JumpGreaterOrEqual:
+ ok = value >= inV
+ case JumpLessOrEqual:
+ ok = value <= inV
+ case JumpBitsSet:
+ ok = (value & inV) != 0
+ case JumpBitsNotSet:
+ ok = (value & inV) == 0
+ }
+
+ if ok {
+ return int(ins.SkipTrue)
+ }
+
+ return int(ins.SkipFalse)
+}
+
+func loadAbsolute(ins LoadAbsolute, in []byte) (uint32, bool) {
+ offset := int(ins.Off)
+ size := int(ins.Size)
+
+ return loadCommon(in, offset, size)
+}
+
+func loadConstant(ins LoadConstant, regA uint32, regX uint32) (uint32, uint32) {
+ switch ins.Dst {
+ case RegA:
+ regA = ins.Val
+ case RegX:
+ regX = ins.Val
+ }
+
+ return regA, regX
+}
+
+func loadExtension(ins LoadExtension, in []byte) uint32 {
+ switch ins.Num {
+ case ExtLen:
+ return uint32(len(in))
+ default:
+ panic(fmt.Sprintf("unimplemented extension: %d", ins.Num))
+ }
+}
+
+func loadIndirect(ins LoadIndirect, in []byte, regX uint32) (uint32, bool) {
+ offset := int(ins.Off) + int(regX)
+ size := int(ins.Size)
+
+ return loadCommon(in, offset, size)
+}
+
+func loadMemShift(ins LoadMemShift, in []byte) (uint32, bool) {
+ offset := int(ins.Off)
+
+ if !inBounds(len(in), offset, 0) {
+ return 0, false
+ }
+
+ // Mask off high 4 bits and multiply low 4 bits by 4
+ return uint32(in[offset]&0x0f) * 4, true
+}
+
+func inBounds(inLen int, offset int, size int) bool {
+ return offset+size <= inLen
+}
+
+func loadCommon(in []byte, offset int, size int) (uint32, bool) {
+ if !inBounds(len(in), offset, size) {
+ return 0, false
+ }
+
+ switch size {
+ case 1:
+ return uint32(in[offset]), true
+ case 2:
+ return uint32(binary.BigEndian.Uint16(in[offset : offset+size])), true
+ case 4:
+ return uint32(binary.BigEndian.Uint32(in[offset : offset+size])), true
+ default:
+ panic(fmt.Sprintf("invalid load size: %d", size))
+ }
+}
+
+func loadScratch(ins LoadScratch, regScratch [16]uint32, regA uint32, regX uint32) (uint32, uint32) {
+ switch ins.Dst {
+ case RegA:
+ regA = regScratch[ins.N]
+ case RegX:
+ regX = regScratch[ins.N]
+ }
+
+ return regA, regX
+}
+
+func storeScratch(ins StoreScratch, regScratch [16]uint32, regA uint32, regX uint32) [16]uint32 {
+ switch ins.Src {
+ case RegA:
+ regScratch[ins.N] = regA
+ case RegX:
+ regScratch[ins.N] = regX
+ }
+
+ return regScratch
+}
diff --git a/vendor/golang.org/x/net/bpf/vm_jump_test.go b/vendor/golang.org/x/net/bpf/vm_jump_test.go
new file mode 100644
index 0000000..e0a3a98
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/vm_jump_test.go
@@ -0,0 +1,380 @@
+// Copyright 2016 The Go 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 bpf_test
+
+import (
+ "testing"
+
+ "golang.org/x/net/bpf"
+)
+
+func TestVMJumpOne(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.Jump{
+ Skip: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 9,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 1,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 1, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpOutOfProgram(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.Jump{
+ Skip: 1,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "cannot jump 1 instructions; jumping past program bounds" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMJumpIfTrueOutOfProgram(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.JumpIf{
+ Cond: bpf.JumpEqual,
+ SkipTrue: 2,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "cannot jump 2 instructions in true case; jumping past program bounds" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMJumpIfFalseOutOfProgram(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.JumpIf{
+ Cond: bpf.JumpEqual,
+ SkipFalse: 3,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "cannot jump 3 instructions in false case; jumping past program bounds" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMJumpIfEqual(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.JumpIf{
+ Cond: bpf.JumpEqual,
+ Val: 1,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 9,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 1,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 1, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfNotEqual(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.JumpIf{
+ Cond: bpf.JumpNotEqual,
+ Val: 1,
+ SkipFalse: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 9,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 1,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 1, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfGreaterThan(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 4,
+ },
+ bpf.JumpIf{
+ Cond: bpf.JumpGreaterThan,
+ Val: 0x00010202,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 12,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1, 2, 3,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 4, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfLessThan(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 4,
+ },
+ bpf.JumpIf{
+ Cond: bpf.JumpLessThan,
+ Val: 0xff010203,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 12,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1, 2, 3,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 4, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfGreaterOrEqual(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 4,
+ },
+ bpf.JumpIf{
+ Cond: bpf.JumpGreaterOrEqual,
+ Val: 0x00010203,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 12,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1, 2, 3,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 4, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfLessOrEqual(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 4,
+ },
+ bpf.JumpIf{
+ Cond: bpf.JumpLessOrEqual,
+ Val: 0xff010203,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 12,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1, 2, 3,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 4, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfBitsSet(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 2,
+ },
+ bpf.JumpIf{
+ Cond: bpf.JumpBitsSet,
+ Val: 0x1122,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 10,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x01, 0x02,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 2, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMJumpIfBitsNotSet(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 2,
+ },
+ bpf.JumpIf{
+ Cond: bpf.JumpBitsNotSet,
+ Val: 0x1221,
+ SkipTrue: 1,
+ },
+ bpf.RetConstant{
+ Val: 0,
+ },
+ bpf.RetConstant{
+ Val: 10,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x01, 0x02,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 2, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
diff --git a/vendor/golang.org/x/net/bpf/vm_load_test.go b/vendor/golang.org/x/net/bpf/vm_load_test.go
new file mode 100644
index 0000000..04578b6
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/vm_load_test.go
@@ -0,0 +1,246 @@
+// Copyright 2016 The Go 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 bpf_test
+
+import (
+ "net"
+ "testing"
+
+ "golang.org/x/net/bpf"
+ "golang.org/x/net/ipv4"
+)
+
+func TestVMLoadAbsoluteOffsetOutOfBounds(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 100,
+ Size: 2,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1, 2, 3,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 0, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMLoadAbsoluteOffsetPlusSizeOutOfBounds(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 2,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 0, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMLoadAbsoluteBadInstructionSize(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Size: 5,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "assembling instruction 1: invalid load byte length 0" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMLoadConstantOK(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadConstant{
+ Dst: bpf.RegX,
+ Val: 9,
+ },
+ bpf.TXA{},
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 1, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMLoadIndirectOutOfBounds(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadIndirect{
+ Off: 100,
+ Size: 1,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 0, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMLoadMemShiftOutOfBounds(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadMemShift{
+ Off: 100,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 0, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+const (
+ dhcp4Port = 53
+)
+
+func TestVMLoadMemShiftLoadIndirectNoResult(t *testing.T) {
+ vm, in, done := testDHCPv4(t)
+ defer done()
+
+ // Append mostly empty UDP header with incorrect DHCPv4 port
+ in = append(in, []byte{
+ 0, 0,
+ 0, dhcp4Port + 1,
+ 0, 0,
+ 0, 0,
+ }...)
+
+ out, err := vm.Run(in)
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 0, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMLoadMemShiftLoadIndirectOK(t *testing.T) {
+ vm, in, done := testDHCPv4(t)
+ defer done()
+
+ // Append mostly empty UDP header with correct DHCPv4 port
+ in = append(in, []byte{
+ 0, 0,
+ 0, dhcp4Port,
+ 0, 0,
+ 0, 0,
+ }...)
+
+ out, err := vm.Run(in)
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := len(in)-8, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func testDHCPv4(t *testing.T) (virtualMachine, []byte, func()) {
+ // DHCPv4 test data courtesy of David Anderson:
+ // https://github.com/google/netboot/blob/master/dhcp4/conn_linux.go#L59-L70
+ vm, done, err := testVM(t, []bpf.Instruction{
+ // Load IPv4 packet length
+ bpf.LoadMemShift{Off: 8},
+ // Get UDP dport
+ bpf.LoadIndirect{Off: 8 + 2, Size: 2},
+ // Correct dport?
+ bpf.JumpIf{Cond: bpf.JumpEqual, Val: dhcp4Port, SkipFalse: 1},
+ // Accept
+ bpf.RetConstant{Val: 1500},
+ // Ignore
+ bpf.RetConstant{Val: 0},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+
+ // Minimal requirements to make a valid IPv4 header
+ h := &ipv4.Header{
+ Len: ipv4.HeaderLen,
+ Src: net.IPv4(192, 168, 1, 1),
+ Dst: net.IPv4(192, 168, 1, 2),
+ }
+ hb, err := h.Marshal()
+ if err != nil {
+ t.Fatalf("failed to marshal IPv4 header: %v", err)
+ }
+
+ hb = append([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ }, hb...)
+
+ return vm, hb, done
+}
diff --git a/vendor/golang.org/x/net/bpf/vm_ret_test.go b/vendor/golang.org/x/net/bpf/vm_ret_test.go
new file mode 100644
index 0000000..2d86eae
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/vm_ret_test.go
@@ -0,0 +1,115 @@
+// Copyright 2016 The Go 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 bpf_test
+
+import (
+ "testing"
+
+ "golang.org/x/net/bpf"
+)
+
+func TestVMRetA(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 9,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 1, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMRetALargerThanInput(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 2,
+ },
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 255,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 2, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMRetConstant(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.RetConstant{
+ Val: 9,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 1, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMRetConstantLargerThanInput(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.RetConstant{
+ Val: 16,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0, 1,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 2, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
diff --git a/vendor/golang.org/x/net/bpf/vm_scratch_test.go b/vendor/golang.org/x/net/bpf/vm_scratch_test.go
new file mode 100644
index 0000000..e600e3c
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/vm_scratch_test.go
@@ -0,0 +1,247 @@
+// Copyright 2016 The Go 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 bpf_test
+
+import (
+ "testing"
+
+ "golang.org/x/net/bpf"
+)
+
+func TestVMStoreScratchInvalidScratchRegisterTooSmall(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.StoreScratch{
+ Src: bpf.RegA,
+ N: -1,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "assembling instruction 1: invalid scratch slot -1" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMStoreScratchInvalidScratchRegisterTooLarge(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.StoreScratch{
+ Src: bpf.RegA,
+ N: 16,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "assembling instruction 1: invalid scratch slot 16" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMStoreScratchUnknownSourceRegister(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.StoreScratch{
+ Src: 100,
+ N: 0,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "assembling instruction 1: invalid source register 100" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMLoadScratchInvalidScratchRegisterTooSmall(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.LoadScratch{
+ Dst: bpf.RegX,
+ N: -1,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "assembling instruction 1: invalid scratch slot -1" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMLoadScratchInvalidScratchRegisterTooLarge(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.LoadScratch{
+ Dst: bpf.RegX,
+ N: 16,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "assembling instruction 1: invalid scratch slot 16" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMLoadScratchUnknownDestinationRegister(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.LoadScratch{
+ Dst: 100,
+ N: 0,
+ },
+ bpf.RetA{},
+ })
+ if errStr(err) != "assembling instruction 1: invalid target register 100" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMStoreScratchLoadScratchOneValue(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ // Load byte 255
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ // Copy to X and store in scratch[0]
+ bpf.TAX{},
+ bpf.StoreScratch{
+ Src: bpf.RegX,
+ N: 0,
+ },
+ // Load byte 1
+ bpf.LoadAbsolute{
+ Off: 9,
+ Size: 1,
+ },
+ // Overwrite 1 with 255 from scratch[0]
+ bpf.LoadScratch{
+ Dst: bpf.RegA,
+ N: 0,
+ },
+ // Return 255
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 255, 1, 2,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 3, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
+
+func TestVMStoreScratchLoadScratchMultipleValues(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ // Load byte 10
+ bpf.LoadAbsolute{
+ Off: 8,
+ Size: 1,
+ },
+ // Store in scratch[0]
+ bpf.StoreScratch{
+ Src: bpf.RegA,
+ N: 0,
+ },
+ // Load byte 20
+ bpf.LoadAbsolute{
+ Off: 9,
+ Size: 1,
+ },
+ // Store in scratch[1]
+ bpf.StoreScratch{
+ Src: bpf.RegA,
+ N: 1,
+ },
+ // Load byte 30
+ bpf.LoadAbsolute{
+ Off: 10,
+ Size: 1,
+ },
+ // Store in scratch[2]
+ bpf.StoreScratch{
+ Src: bpf.RegA,
+ N: 2,
+ },
+ // Load byte 1
+ bpf.LoadAbsolute{
+ Off: 11,
+ Size: 1,
+ },
+ // Store in scratch[3]
+ bpf.StoreScratch{
+ Src: bpf.RegA,
+ N: 3,
+ },
+ // Load in byte 10 to X
+ bpf.LoadScratch{
+ Dst: bpf.RegX,
+ N: 0,
+ },
+ // Copy X -> A
+ bpf.TXA{},
+ // Verify value is 10
+ bpf.JumpIf{
+ Cond: bpf.JumpEqual,
+ Val: 10,
+ SkipTrue: 1,
+ },
+ // Fail test if incorrect
+ bpf.RetConstant{
+ Val: 0,
+ },
+ // Load in byte 20 to A
+ bpf.LoadScratch{
+ Dst: bpf.RegA,
+ N: 1,
+ },
+ // Verify value is 20
+ bpf.JumpIf{
+ Cond: bpf.JumpEqual,
+ Val: 20,
+ SkipTrue: 1,
+ },
+ // Fail test if incorrect
+ bpf.RetConstant{
+ Val: 0,
+ },
+ // Load in byte 30 to A
+ bpf.LoadScratch{
+ Dst: bpf.RegA,
+ N: 2,
+ },
+ // Verify value is 30
+ bpf.JumpIf{
+ Cond: bpf.JumpEqual,
+ Val: 30,
+ SkipTrue: 1,
+ },
+ // Fail test if incorrect
+ bpf.RetConstant{
+ Val: 0,
+ },
+ // Return first two bytes on success
+ bpf.RetConstant{
+ Val: 10,
+ },
+ })
+ if err != nil {
+ t.Fatalf("failed to load BPF program: %v", err)
+ }
+ defer done()
+
+ out, err := vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 10, 20, 30, 1,
+ })
+ if err != nil {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+ if want, got := 2, out; want != got {
+ t.Fatalf("unexpected number of output bytes:\n- want: %d\n- got: %d",
+ want, got)
+ }
+}
diff --git a/vendor/golang.org/x/net/bpf/vm_test.go b/vendor/golang.org/x/net/bpf/vm_test.go
new file mode 100644
index 0000000..6bd4dd5
--- /dev/null
+++ b/vendor/golang.org/x/net/bpf/vm_test.go
@@ -0,0 +1,144 @@
+// Copyright 2016 The Go 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 bpf_test
+
+import (
+ "fmt"
+ "testing"
+
+ "golang.org/x/net/bpf"
+)
+
+var _ bpf.Instruction = unknown{}
+
+type unknown struct{}
+
+func (unknown) Assemble() (bpf.RawInstruction, error) {
+ return bpf.RawInstruction{}, nil
+}
+
+func TestVMUnknownInstruction(t *testing.T) {
+ vm, done, err := testVM(t, []bpf.Instruction{
+ bpf.LoadConstant{
+ Dst: bpf.RegA,
+ Val: 100,
+ },
+ // Should terminate the program with an error immediately
+ unknown{},
+ bpf.RetA{},
+ })
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ defer done()
+
+ _, err = vm.Run([]byte{
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x00,
+ })
+ if errStr(err) != "unknown Instruction at index 1: bpf_test.unknown" {
+ t.Fatalf("unexpected error while running program: %v", err)
+ }
+}
+
+func TestVMNoReturnInstruction(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{
+ bpf.LoadConstant{
+ Dst: bpf.RegA,
+ Val: 1,
+ },
+ })
+ if errStr(err) != "BPF program must end with RetA or RetConstant" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestVMNoInputInstructions(t *testing.T) {
+ _, _, err := testVM(t, []bpf.Instruction{})
+ if errStr(err) != "one or more Instructions must be specified" {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+// ExampleNewVM demonstrates usage of a VM, using an Ethernet frame
+// as input and checking its EtherType to determine if it should be accepted.
+func ExampleNewVM() {
+ // Offset | Length | Comment
+ // -------------------------
+ // 00 | 06 | Ethernet destination MAC address
+ // 06 | 06 | Ethernet source MAC address
+ // 12 | 02 | Ethernet EtherType
+ const (
+ etOff = 12
+ etLen = 2
+
+ etARP = 0x0806
+ )
+
+ // Set up a VM to filter traffic based on if its EtherType
+ // matches the ARP EtherType.
+ vm, err := bpf.NewVM([]bpf.Instruction{
+ // Load EtherType value from Ethernet header
+ bpf.LoadAbsolute{
+ Off: etOff,
+ Size: etLen,
+ },
+ // If EtherType is equal to the ARP EtherType, jump to allow
+ // packet to be accepted
+ bpf.JumpIf{
+ Cond: bpf.JumpEqual,
+ Val: etARP,
+ SkipTrue: 1,
+ },
+ // EtherType does not match the ARP EtherType
+ bpf.RetConstant{
+ Val: 0,
+ },
+ // EtherType matches the ARP EtherType, accept up to 1500
+ // bytes of packet
+ bpf.RetConstant{
+ Val: 1500,
+ },
+ })
+ if err != nil {
+ panic(fmt.Sprintf("failed to load BPF program: %v", err))
+ }
+
+ // Create an Ethernet frame with the ARP EtherType for testing
+ frame := []byte{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
+ 0x08, 0x06,
+ // Payload omitted for brevity
+ }
+
+ // Run our VM's BPF program using the Ethernet frame as input
+ out, err := vm.Run(frame)
+ if err != nil {
+ panic(fmt.Sprintf("failed to accept Ethernet frame: %v", err))
+ }
+
+ // BPF VM can return a byte count greater than the number of input
+ // bytes, so trim the output to match the input byte length
+ if out > len(frame) {
+ out = len(frame)
+ }
+
+ fmt.Printf("out: %d bytes", out)
+
+ // Output:
+ // out: 14 bytes
+}
+
+// errStr returns the string representation of an error, or
+// "" if it is nil.
+func errStr(err error) string {
+ if err == nil {
+ return ""
+ }
+
+ return err.Error()
+}
diff --git a/vendor/golang.org/x/net/codereview.cfg b/vendor/golang.org/x/net/codereview.cfg
new file mode 100644
index 0000000..3f8b14b
--- /dev/null
+++ b/vendor/golang.org/x/net/codereview.cfg
@@ -0,0 +1 @@
+issuerepo: golang/go
diff --git a/vendor/golang.org/x/net/context/context.go b/vendor/golang.org/x/net/context/context.go
new file mode 100644
index 0000000..a3c021d
--- /dev/null
+++ b/vendor/golang.org/x/net/context/context.go
@@ -0,0 +1,56 @@
+// Copyright 2014 The Go 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 context defines the Context type, which carries deadlines,
+// cancelation signals, and other request-scoped values across API boundaries
+// and between processes.
+// As of Go 1.7 this package is available in the standard library under the
+// name context. https://golang.org/pkg/context.
+//
+// Incoming requests to a server should create a Context, and outgoing calls to
+// servers should accept a Context. The chain of function calls between must
+// propagate the Context, optionally replacing it with a modified copy created
+// using WithDeadline, WithTimeout, WithCancel, or WithValue.
+//
+// Programs that use Contexts should follow these rules to keep interfaces
+// consistent across packages and enable static analysis tools to check context
+// propagation:
+//
+// Do not store Contexts inside a struct type; instead, pass a Context
+// explicitly to each function that needs it. The Context should be the first
+// parameter, typically named ctx:
+//
+// func DoSomething(ctx context.Context, arg Arg) error {
+// // ... use ctx ...
+// }
+//
+// Do not pass a nil Context, even if a function permits it. Pass context.TODO
+// if you are unsure about which Context to use.
+//
+// Use context Values only for request-scoped data that transits processes and
+// APIs, not for passing optional parameters to functions.
+//
+// The same Context may be passed to functions running in different goroutines;
+// Contexts are safe for simultaneous use by multiple goroutines.
+//
+// See http://blog.golang.org/context for example code for a server that uses
+// Contexts.
+package context // import "golang.org/x/net/context"
+
+// Background returns a non-nil, empty Context. It is never canceled, has no
+// values, and has no deadline. It is typically used by the main function,
+// initialization, and tests, and as the top-level Context for incoming
+// requests.
+func Background() Context {
+ return background
+}
+
+// TODO returns a non-nil, empty Context. Code should use context.TODO when
+// it's unclear which Context to use or it is not yet available (because the
+// surrounding function has not yet been extended to accept a Context
+// parameter). TODO is recognized by static analysis tools that determine
+// whether Contexts are propagated correctly in a program.
+func TODO() Context {
+ return todo
+}
diff --git a/vendor/golang.org/x/net/context/context_test.go b/vendor/golang.org/x/net/context/context_test.go
new file mode 100644
index 0000000..6284413
--- /dev/null
+++ b/vendor/golang.org/x/net/context/context_test.go
@@ -0,0 +1,583 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.7
+
+package context
+
+import (
+ "fmt"
+ "math/rand"
+ "runtime"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+)
+
+// otherContext is a Context that's not one of the types defined in context.go.
+// This lets us test code paths that differ based on the underlying type of the
+// Context.
+type otherContext struct {
+ Context
+}
+
+func TestBackground(t *testing.T) {
+ c := Background()
+ if c == nil {
+ t.Fatalf("Background returned nil")
+ }
+ select {
+ case x := <-c.Done():
+ t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
+ default:
+ }
+ if got, want := fmt.Sprint(c), "context.Background"; got != want {
+ t.Errorf("Background().String() = %q want %q", got, want)
+ }
+}
+
+func TestTODO(t *testing.T) {
+ c := TODO()
+ if c == nil {
+ t.Fatalf("TODO returned nil")
+ }
+ select {
+ case x := <-c.Done():
+ t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
+ default:
+ }
+ if got, want := fmt.Sprint(c), "context.TODO"; got != want {
+ t.Errorf("TODO().String() = %q want %q", got, want)
+ }
+}
+
+func TestWithCancel(t *testing.T) {
+ c1, cancel := WithCancel(Background())
+
+ if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want {
+ t.Errorf("c1.String() = %q want %q", got, want)
+ }
+
+ o := otherContext{c1}
+ c2, _ := WithCancel(o)
+ contexts := []Context{c1, o, c2}
+
+ for i, c := range contexts {
+ if d := c.Done(); d == nil {
+ t.Errorf("c[%d].Done() == %v want non-nil", i, d)
+ }
+ if e := c.Err(); e != nil {
+ t.Errorf("c[%d].Err() == %v want nil", i, e)
+ }
+
+ select {
+ case x := <-c.Done():
+ t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
+ default:
+ }
+ }
+
+ cancel()
+ time.Sleep(100 * time.Millisecond) // let cancelation propagate
+
+ for i, c := range contexts {
+ select {
+ case <-c.Done():
+ default:
+ t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i)
+ }
+ if e := c.Err(); e != Canceled {
+ t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled)
+ }
+ }
+}
+
+func TestParentFinishesChild(t *testing.T) {
+ // Context tree:
+ // parent -> cancelChild
+ // parent -> valueChild -> timerChild
+ parent, cancel := WithCancel(Background())
+ cancelChild, stop := WithCancel(parent)
+ defer stop()
+ valueChild := WithValue(parent, "key", "value")
+ timerChild, stop := WithTimeout(valueChild, 10000*time.Hour)
+ defer stop()
+
+ select {
+ case x := <-parent.Done():
+ t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
+ case x := <-cancelChild.Done():
+ t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x)
+ case x := <-timerChild.Done():
+ t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x)
+ case x := <-valueChild.Done():
+ t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x)
+ default:
+ }
+
+ // The parent's children should contain the two cancelable children.
+ pc := parent.(*cancelCtx)
+ cc := cancelChild.(*cancelCtx)
+ tc := timerChild.(*timerCtx)
+ pc.mu.Lock()
+ if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] {
+ t.Errorf("bad linkage: pc.children = %v, want %v and %v",
+ pc.children, cc, tc)
+ }
+ pc.mu.Unlock()
+
+ if p, ok := parentCancelCtx(cc.Context); !ok || p != pc {
+ t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc)
+ }
+ if p, ok := parentCancelCtx(tc.Context); !ok || p != pc {
+ t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc)
+ }
+
+ cancel()
+
+ pc.mu.Lock()
+ if len(pc.children) != 0 {
+ t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children)
+ }
+ pc.mu.Unlock()
+
+ // parent and children should all be finished.
+ check := func(ctx Context, name string) {
+ select {
+ case <-ctx.Done():
+ default:
+ t.Errorf("<-%s.Done() blocked, but shouldn't have", name)
+ }
+ if e := ctx.Err(); e != Canceled {
+ t.Errorf("%s.Err() == %v want %v", name, e, Canceled)
+ }
+ }
+ check(parent, "parent")
+ check(cancelChild, "cancelChild")
+ check(valueChild, "valueChild")
+ check(timerChild, "timerChild")
+
+ // WithCancel should return a canceled context on a canceled parent.
+ precanceledChild := WithValue(parent, "key", "value")
+ select {
+ case <-precanceledChild.Done():
+ default:
+ t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have")
+ }
+ if e := precanceledChild.Err(); e != Canceled {
+ t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled)
+ }
+}
+
+func TestChildFinishesFirst(t *testing.T) {
+ cancelable, stop := WithCancel(Background())
+ defer stop()
+ for _, parent := range []Context{Background(), cancelable} {
+ child, cancel := WithCancel(parent)
+
+ select {
+ case x := <-parent.Done():
+ t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
+ case x := <-child.Done():
+ t.Errorf("<-child.Done() == %v want nothing (it should block)", x)
+ default:
+ }
+
+ cc := child.(*cancelCtx)
+ pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background()
+ if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) {
+ t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok)
+ }
+
+ if pcok {
+ pc.mu.Lock()
+ if len(pc.children) != 1 || !pc.children[cc] {
+ t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc)
+ }
+ pc.mu.Unlock()
+ }
+
+ cancel()
+
+ if pcok {
+ pc.mu.Lock()
+ if len(pc.children) != 0 {
+ t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children)
+ }
+ pc.mu.Unlock()
+ }
+
+ // child should be finished.
+ select {
+ case <-child.Done():
+ default:
+ t.Errorf("<-child.Done() blocked, but shouldn't have")
+ }
+ if e := child.Err(); e != Canceled {
+ t.Errorf("child.Err() == %v want %v", e, Canceled)
+ }
+
+ // parent should not be finished.
+ select {
+ case x := <-parent.Done():
+ t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
+ default:
+ }
+ if e := parent.Err(); e != nil {
+ t.Errorf("parent.Err() == %v want nil", e)
+ }
+ }
+}
+
+func testDeadline(c Context, wait time.Duration, t *testing.T) {
+ select {
+ case <-time.After(wait):
+ t.Fatalf("context should have timed out")
+ case <-c.Done():
+ }
+ if e := c.Err(); e != DeadlineExceeded {
+ t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded)
+ }
+}
+
+func TestDeadline(t *testing.T) {
+ t.Parallel()
+ const timeUnit = 500 * time.Millisecond
+ c, _ := WithDeadline(Background(), time.Now().Add(1*timeUnit))
+ if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
+ t.Errorf("c.String() = %q want prefix %q", got, prefix)
+ }
+ testDeadline(c, 2*timeUnit, t)
+
+ c, _ = WithDeadline(Background(), time.Now().Add(1*timeUnit))
+ o := otherContext{c}
+ testDeadline(o, 2*timeUnit, t)
+
+ c, _ = WithDeadline(Background(), time.Now().Add(1*timeUnit))
+ o = otherContext{c}
+ c, _ = WithDeadline(o, time.Now().Add(3*timeUnit))
+ testDeadline(c, 2*timeUnit, t)
+}
+
+func TestTimeout(t *testing.T) {
+ t.Parallel()
+ const timeUnit = 500 * time.Millisecond
+ c, _ := WithTimeout(Background(), 1*timeUnit)
+ if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
+ t.Errorf("c.String() = %q want prefix %q", got, prefix)
+ }
+ testDeadline(c, 2*timeUnit, t)
+
+ c, _ = WithTimeout(Background(), 1*timeUnit)
+ o := otherContext{c}
+ testDeadline(o, 2*timeUnit, t)
+
+ c, _ = WithTimeout(Background(), 1*timeUnit)
+ o = otherContext{c}
+ c, _ = WithTimeout(o, 3*timeUnit)
+ testDeadline(c, 2*timeUnit, t)
+}
+
+func TestCanceledTimeout(t *testing.T) {
+ t.Parallel()
+ const timeUnit = 500 * time.Millisecond
+ c, _ := WithTimeout(Background(), 2*timeUnit)
+ o := otherContext{c}
+ c, cancel := WithTimeout(o, 4*timeUnit)
+ cancel()
+ time.Sleep(1 * timeUnit) // let cancelation propagate
+ select {
+ case <-c.Done():
+ default:
+ t.Errorf("<-c.Done() blocked, but shouldn't have")
+ }
+ if e := c.Err(); e != Canceled {
+ t.Errorf("c.Err() == %v want %v", e, Canceled)
+ }
+}
+
+type key1 int
+type key2 int
+
+var k1 = key1(1)
+var k2 = key2(1) // same int as k1, different type
+var k3 = key2(3) // same type as k2, different int
+
+func TestValues(t *testing.T) {
+ check := func(c Context, nm, v1, v2, v3 string) {
+ if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {
+ t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)
+ }
+ if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {
+ t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)
+ }
+ if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {
+ t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)
+ }
+ }
+
+ c0 := Background()
+ check(c0, "c0", "", "", "")
+
+ c1 := WithValue(Background(), k1, "c1k1")
+ check(c1, "c1", "c1k1", "", "")
+
+ if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want {
+ t.Errorf("c.String() = %q want %q", got, want)
+ }
+
+ c2 := WithValue(c1, k2, "c2k2")
+ check(c2, "c2", "c1k1", "c2k2", "")
+
+ c3 := WithValue(c2, k3, "c3k3")
+ check(c3, "c2", "c1k1", "c2k2", "c3k3")
+
+ c4 := WithValue(c3, k1, nil)
+ check(c4, "c4", "", "c2k2", "c3k3")
+
+ o0 := otherContext{Background()}
+ check(o0, "o0", "", "", "")
+
+ o1 := otherContext{WithValue(Background(), k1, "c1k1")}
+ check(o1, "o1", "c1k1", "", "")
+
+ o2 := WithValue(o1, k2, "o2k2")
+ check(o2, "o2", "c1k1", "o2k2", "")
+
+ o3 := otherContext{c4}
+ check(o3, "o3", "", "c2k2", "c3k3")
+
+ o4 := WithValue(o3, k3, nil)
+ check(o4, "o4", "", "c2k2", "")
+}
+
+func TestAllocs(t *testing.T) {
+ bg := Background()
+ for _, test := range []struct {
+ desc string
+ f func()
+ limit float64
+ gccgoLimit float64
+ }{
+ {
+ desc: "Background()",
+ f: func() { Background() },
+ limit: 0,
+ gccgoLimit: 0,
+ },
+ {
+ desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1),
+ f: func() {
+ c := WithValue(bg, k1, nil)
+ c.Value(k1)
+ },
+ limit: 3,
+ gccgoLimit: 3,
+ },
+ {
+ desc: "WithTimeout(bg, 15*time.Millisecond)",
+ f: func() {
+ c, _ := WithTimeout(bg, 15*time.Millisecond)
+ <-c.Done()
+ },
+ limit: 8,
+ gccgoLimit: 16,
+ },
+ {
+ desc: "WithCancel(bg)",
+ f: func() {
+ c, cancel := WithCancel(bg)
+ cancel()
+ <-c.Done()
+ },
+ limit: 5,
+ gccgoLimit: 8,
+ },
+ {
+ desc: "WithTimeout(bg, 100*time.Millisecond)",
+ f: func() {
+ c, cancel := WithTimeout(bg, 100*time.Millisecond)
+ cancel()
+ <-c.Done()
+ },
+ limit: 8,
+ gccgoLimit: 25,
+ },
+ } {
+ limit := test.limit
+ if runtime.Compiler == "gccgo" {
+ // gccgo does not yet do escape analysis.
+ // TODO(iant): Remove this when gccgo does do escape analysis.
+ limit = test.gccgoLimit
+ }
+ if n := testing.AllocsPerRun(100, test.f); n > limit {
+ t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit))
+ }
+ }
+}
+
+func TestSimultaneousCancels(t *testing.T) {
+ root, cancel := WithCancel(Background())
+ m := map[Context]CancelFunc{root: cancel}
+ q := []Context{root}
+ // Create a tree of contexts.
+ for len(q) != 0 && len(m) < 100 {
+ parent := q[0]
+ q = q[1:]
+ for i := 0; i < 4; i++ {
+ ctx, cancel := WithCancel(parent)
+ m[ctx] = cancel
+ q = append(q, ctx)
+ }
+ }
+ // Start all the cancels in a random order.
+ var wg sync.WaitGroup
+ wg.Add(len(m))
+ for _, cancel := range m {
+ go func(cancel CancelFunc) {
+ cancel()
+ wg.Done()
+ }(cancel)
+ }
+ // Wait on all the contexts in a random order.
+ for ctx := range m {
+ select {
+ case <-ctx.Done():
+ case <-time.After(1 * time.Second):
+ buf := make([]byte, 10<<10)
+ n := runtime.Stack(buf, true)
+ t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n])
+ }
+ }
+ // Wait for all the cancel functions to return.
+ done := make(chan struct{})
+ go func() {
+ wg.Wait()
+ close(done)
+ }()
+ select {
+ case <-done:
+ case <-time.After(1 * time.Second):
+ buf := make([]byte, 10<<10)
+ n := runtime.Stack(buf, true)
+ t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n])
+ }
+}
+
+func TestInterlockedCancels(t *testing.T) {
+ parent, cancelParent := WithCancel(Background())
+ child, cancelChild := WithCancel(parent)
+ go func() {
+ parent.Done()
+ cancelChild()
+ }()
+ cancelParent()
+ select {
+ case <-child.Done():
+ case <-time.After(1 * time.Second):
+ buf := make([]byte, 10<<10)
+ n := runtime.Stack(buf, true)
+ t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n])
+ }
+}
+
+func TestLayersCancel(t *testing.T) {
+ testLayers(t, time.Now().UnixNano(), false)
+}
+
+func TestLayersTimeout(t *testing.T) {
+ testLayers(t, time.Now().UnixNano(), true)
+}
+
+func testLayers(t *testing.T, seed int64, testTimeout bool) {
+ rand.Seed(seed)
+ errorf := func(format string, a ...interface{}) {
+ t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...)
+ }
+ const (
+ timeout = 200 * time.Millisecond
+ minLayers = 30
+ )
+ type value int
+ var (
+ vals []*value
+ cancels []CancelFunc
+ numTimers int
+ ctx = Background()
+ )
+ for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ {
+ switch rand.Intn(3) {
+ case 0:
+ v := new(value)
+ ctx = WithValue(ctx, v, v)
+ vals = append(vals, v)
+ case 1:
+ var cancel CancelFunc
+ ctx, cancel = WithCancel(ctx)
+ cancels = append(cancels, cancel)
+ case 2:
+ var cancel CancelFunc
+ ctx, cancel = WithTimeout(ctx, timeout)
+ cancels = append(cancels, cancel)
+ numTimers++
+ }
+ }
+ checkValues := func(when string) {
+ for _, key := range vals {
+ if val := ctx.Value(key).(*value); key != val {
+ errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key)
+ }
+ }
+ }
+ select {
+ case <-ctx.Done():
+ errorf("ctx should not be canceled yet")
+ default:
+ }
+ if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) {
+ t.Errorf("ctx.String() = %q want prefix %q", s, prefix)
+ }
+ t.Log(ctx)
+ checkValues("before cancel")
+ if testTimeout {
+ select {
+ case <-ctx.Done():
+ case <-time.After(timeout + 100*time.Millisecond):
+ errorf("ctx should have timed out")
+ }
+ checkValues("after timeout")
+ } else {
+ cancel := cancels[rand.Intn(len(cancels))]
+ cancel()
+ select {
+ case <-ctx.Done():
+ default:
+ errorf("ctx should be canceled")
+ }
+ checkValues("after cancel")
+ }
+}
+
+func TestCancelRemoves(t *testing.T) {
+ checkChildren := func(when string, ctx Context, want int) {
+ if got := len(ctx.(*cancelCtx).children); got != want {
+ t.Errorf("%s: context has %d children, want %d", when, got, want)
+ }
+ }
+
+ ctx, _ := WithCancel(Background())
+ checkChildren("after creation", ctx, 0)
+ _, cancel := WithCancel(ctx)
+ checkChildren("with WithCancel child ", ctx, 1)
+ cancel()
+ checkChildren("after cancelling WithCancel child", ctx, 0)
+
+ ctx, _ = WithCancel(Background())
+ checkChildren("after creation", ctx, 0)
+ _, cancel = WithTimeout(ctx, 60*time.Minute)
+ checkChildren("with WithTimeout child ", ctx, 1)
+ cancel()
+ checkChildren("after cancelling WithTimeout child", ctx, 0)
+}
diff --git a/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go
new file mode 100644
index 0000000..606cf1f
--- /dev/null
+++ b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go
@@ -0,0 +1,74 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.7
+
+// Package ctxhttp provides helper functions for performing context-aware HTTP requests.
+package ctxhttp // import "golang.org/x/net/context/ctxhttp"
+
+import (
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "golang.org/x/net/context"
+)
+
+// Do sends an HTTP request with the provided http.Client and returns
+// an HTTP response.
+//
+// If the client is nil, http.DefaultClient is used.
+//
+// The provided ctx must be non-nil. If it is canceled or times out,
+// ctx.Err() will be returned.
+func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
+ if client == nil {
+ client = http.DefaultClient
+ }
+ resp, err := client.Do(req.WithContext(ctx))
+ // If we got an error, and the context has been canceled,
+ // the context's error is probably more useful.
+ if err != nil {
+ select {
+ case <-ctx.Done():
+ err = ctx.Err()
+ default:
+ }
+ }
+ return resp, err
+}
+
+// Get issues a GET request via the Do function.
+func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return nil, err
+ }
+ return Do(ctx, client, req)
+}
+
+// Head issues a HEAD request via the Do function.
+func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
+ req, err := http.NewRequest("HEAD", url, nil)
+ if err != nil {
+ return nil, err
+ }
+ return Do(ctx, client, req)
+}
+
+// Post issues a POST request via the Do function.
+func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) {
+ req, err := http.NewRequest("POST", url, body)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-Type", bodyType)
+ return Do(ctx, client, req)
+}
+
+// PostForm issues a POST request via the Do function.
+func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) {
+ return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
+}
diff --git a/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_17_test.go b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_17_test.go
new file mode 100644
index 0000000..72411b1
--- /dev/null
+++ b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_17_test.go
@@ -0,0 +1,29 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !plan9,go1.7
+
+package ctxhttp
+
+import (
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "context"
+)
+
+func TestGo17Context(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, "ok")
+ }))
+ defer ts.Close()
+ ctx := context.Background()
+ resp, err := Get(ctx, http.DefaultClient, ts.URL)
+ if resp == nil || err != nil {
+ t.Fatalf("error received from client: %v %v", err, resp)
+ }
+ resp.Body.Close()
+}
diff --git a/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17.go b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17.go
new file mode 100644
index 0000000..926870c
--- /dev/null
+++ b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17.go
@@ -0,0 +1,147 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.7
+
+package ctxhttp // import "golang.org/x/net/context/ctxhttp"
+
+import (
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "golang.org/x/net/context"
+)
+
+func nop() {}
+
+var (
+ testHookContextDoneBeforeHeaders = nop
+ testHookDoReturned = nop
+ testHookDidBodyClose = nop
+)
+
+// Do sends an HTTP request with the provided http.Client and returns an HTTP response.
+// If the client is nil, http.DefaultClient is used.
+// If the context is canceled or times out, ctx.Err() will be returned.
+func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
+ if client == nil {
+ client = http.DefaultClient
+ }
+
+ // TODO(djd): Respect any existing value of req.Cancel.
+ cancel := make(chan struct{})
+ req.Cancel = cancel
+
+ type responseAndError struct {
+ resp *http.Response
+ err error
+ }
+ result := make(chan responseAndError, 1)
+
+ // Make local copies of test hooks closed over by goroutines below.
+ // Prevents data races in tests.
+ testHookDoReturned := testHookDoReturned
+ testHookDidBodyClose := testHookDidBodyClose
+
+ go func() {
+ resp, err := client.Do(req)
+ testHookDoReturned()
+ result <- responseAndError{resp, err}
+ }()
+
+ var resp *http.Response
+
+ select {
+ case <-ctx.Done():
+ testHookContextDoneBeforeHeaders()
+ close(cancel)
+ // Clean up after the goroutine calling client.Do:
+ go func() {
+ if r := <-result; r.resp != nil {
+ testHookDidBodyClose()
+ r.resp.Body.Close()
+ }
+ }()
+ return nil, ctx.Err()
+ case r := <-result:
+ var err error
+ resp, err = r.resp, r.err
+ if err != nil {
+ return resp, err
+ }
+ }
+
+ c := make(chan struct{})
+ go func() {
+ select {
+ case <-ctx.Done():
+ close(cancel)
+ case <-c:
+ // The response's Body is closed.
+ }
+ }()
+ resp.Body = ¬ifyingReader{resp.Body, c}
+
+ return resp, nil
+}
+
+// Get issues a GET request via the Do function.
+func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return nil, err
+ }
+ return Do(ctx, client, req)
+}
+
+// Head issues a HEAD request via the Do function.
+func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
+ req, err := http.NewRequest("HEAD", url, nil)
+ if err != nil {
+ return nil, err
+ }
+ return Do(ctx, client, req)
+}
+
+// Post issues a POST request via the Do function.
+func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) {
+ req, err := http.NewRequest("POST", url, body)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-Type", bodyType)
+ return Do(ctx, client, req)
+}
+
+// PostForm issues a POST request via the Do function.
+func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) {
+ return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
+}
+
+// notifyingReader is an io.ReadCloser that closes the notify channel after
+// Close is called or a Read fails on the underlying ReadCloser.
+type notifyingReader struct {
+ io.ReadCloser
+ notify chan<- struct{}
+}
+
+func (r *notifyingReader) Read(p []byte) (int, error) {
+ n, err := r.ReadCloser.Read(p)
+ if err != nil && r.notify != nil {
+ close(r.notify)
+ r.notify = nil
+ }
+ return n, err
+}
+
+func (r *notifyingReader) Close() error {
+ err := r.ReadCloser.Close()
+ if r.notify != nil {
+ close(r.notify)
+ r.notify = nil
+ }
+ return err
+}
diff --git a/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17_test.go b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17_test.go
new file mode 100644
index 0000000..9159cf0
--- /dev/null
+++ b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17_test.go
@@ -0,0 +1,79 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !plan9,!go1.7
+
+package ctxhttp
+
+import (
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "sync"
+ "testing"
+ "time"
+
+ "golang.org/x/net/context"
+)
+
+// golang.org/issue/14065
+func TestClosesResponseBodyOnCancel(t *testing.T) {
+ defer func() { testHookContextDoneBeforeHeaders = nop }()
+ defer func() { testHookDoReturned = nop }()
+ defer func() { testHookDidBodyClose = nop }()
+
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
+ defer ts.Close()
+
+ ctx, cancel := context.WithCancel(context.Background())
+
+ // closed when Do enters select case <-ctx.Done()
+ enteredDonePath := make(chan struct{})
+
+ testHookContextDoneBeforeHeaders = func() {
+ close(enteredDonePath)
+ }
+
+ testHookDoReturned = func() {
+ // We now have the result (the Flush'd headers) at least,
+ // so we can cancel the request.
+ cancel()
+
+ // But block the client.Do goroutine from sending
+ // until Do enters into the <-ctx.Done() path, since
+ // otherwise if both channels are readable, select
+ // picks a random one.
+ <-enteredDonePath
+ }
+
+ sawBodyClose := make(chan struct{})
+ testHookDidBodyClose = func() { close(sawBodyClose) }
+
+ tr := &http.Transport{}
+ defer tr.CloseIdleConnections()
+ c := &http.Client{Transport: tr}
+ req, _ := http.NewRequest("GET", ts.URL, nil)
+ _, doErr := Do(ctx, c, req)
+
+ select {
+ case <-sawBodyClose:
+ case <-time.After(5 * time.Second):
+ t.Fatal("timeout waiting for body to close")
+ }
+
+ if doErr != ctx.Err() {
+ t.Errorf("Do error = %v; want %v", doErr, ctx.Err())
+ }
+}
+
+type noteCloseConn struct {
+ net.Conn
+ onceClose sync.Once
+ closefn func()
+}
+
+func (c *noteCloseConn) Close() error {
+ c.onceClose.Do(c.closefn)
+ return c.Conn.Close()
+}
diff --git a/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_test.go b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_test.go
new file mode 100644
index 0000000..1e41551
--- /dev/null
+++ b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_test.go
@@ -0,0 +1,105 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !plan9
+
+package ctxhttp
+
+import (
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "golang.org/x/net/context"
+)
+
+const (
+ requestDuration = 100 * time.Millisecond
+ requestBody = "ok"
+)
+
+func okHandler(w http.ResponseWriter, r *http.Request) {
+ time.Sleep(requestDuration)
+ io.WriteString(w, requestBody)
+}
+
+func TestNoTimeout(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(okHandler))
+ defer ts.Close()
+
+ ctx := context.Background()
+ res, err := Get(ctx, nil, ts.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer res.Body.Close()
+ slurp, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if string(slurp) != requestBody {
+ t.Errorf("body = %q; want %q", slurp, requestBody)
+ }
+}
+
+func TestCancelBeforeHeaders(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+
+ blockServer := make(chan struct{})
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ cancel()
+ <-blockServer
+ io.WriteString(w, requestBody)
+ }))
+ defer ts.Close()
+ defer close(blockServer)
+
+ res, err := Get(ctx, nil, ts.URL)
+ if err == nil {
+ res.Body.Close()
+ t.Fatal("Get returned unexpected nil error")
+ }
+ if err != context.Canceled {
+ t.Errorf("err = %v; want %v", err, context.Canceled)
+ }
+}
+
+func TestCancelAfterHangingRequest(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ w.(http.Flusher).Flush()
+ <-w.(http.CloseNotifier).CloseNotify()
+ }))
+ defer ts.Close()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ resp, err := Get(ctx, nil, ts.URL)
+ if err != nil {
+ t.Fatalf("unexpected error in Get: %v", err)
+ }
+
+ // Cancel befer reading the body.
+ // Reading Request.Body should fail, since the request was
+ // canceled before anything was written.
+ cancel()
+
+ done := make(chan struct{})
+
+ go func() {
+ b, err := ioutil.ReadAll(resp.Body)
+ if len(b) != 0 || err == nil {
+ t.Errorf(`Read got (%q, %v); want ("", error)`, b, err)
+ }
+ close(done)
+ }()
+
+ select {
+ case <-time.After(1 * time.Second):
+ t.Errorf("Test timed out")
+ case <-done:
+ }
+}
diff --git a/vendor/golang.org/x/net/context/go17.go b/vendor/golang.org/x/net/context/go17.go
new file mode 100644
index 0000000..d20f52b
--- /dev/null
+++ b/vendor/golang.org/x/net/context/go17.go
@@ -0,0 +1,72 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.7
+
+package context
+
+import (
+ "context" // standard library's context, as of Go 1.7
+ "time"
+)
+
+var (
+ todo = context.TODO()
+ background = context.Background()
+)
+
+// Canceled is the error returned by Context.Err when the context is canceled.
+var Canceled = context.Canceled
+
+// DeadlineExceeded is the error returned by Context.Err when the context's
+// deadline passes.
+var DeadlineExceeded = context.DeadlineExceeded
+
+// WithCancel returns a copy of parent with a new Done channel. The returned
+// context's Done channel is closed when the returned cancel function is called
+// or when the parent context's Done channel is closed, whichever happens first.
+//
+// Canceling this context releases resources associated with it, so code should
+// call cancel as soon as the operations running in this Context complete.
+func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
+ ctx, f := context.WithCancel(parent)
+ return ctx, CancelFunc(f)
+}
+
+// WithDeadline returns a copy of the parent context with the deadline adjusted
+// to be no later than d. If the parent's deadline is already earlier than d,
+// WithDeadline(parent, d) is semantically equivalent to parent. The returned
+// context's Done channel is closed when the deadline expires, when the returned
+// cancel function is called, or when the parent context's Done channel is
+// closed, whichever happens first.
+//
+// Canceling this context releases resources associated with it, so code should
+// call cancel as soon as the operations running in this Context complete.
+func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
+ ctx, f := context.WithDeadline(parent, deadline)
+ return ctx, CancelFunc(f)
+}
+
+// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
+//
+// Canceling this context releases resources associated with it, so code should
+// call cancel as soon as the operations running in this Context complete:
+//
+// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
+// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
+// defer cancel() // releases resources if slowOperation completes before timeout elapses
+// return slowOperation(ctx)
+// }
+func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
+ return WithDeadline(parent, time.Now().Add(timeout))
+}
+
+// WithValue returns a copy of parent in which the value associated with key is
+// val.
+//
+// Use context Values only for request-scoped data that transits processes and
+// APIs, not for passing optional parameters to functions.
+func WithValue(parent Context, key interface{}, val interface{}) Context {
+ return context.WithValue(parent, key, val)
+}
diff --git a/vendor/golang.org/x/net/context/go19.go b/vendor/golang.org/x/net/context/go19.go
new file mode 100644
index 0000000..d88bd1d
--- /dev/null
+++ b/vendor/golang.org/x/net/context/go19.go
@@ -0,0 +1,20 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.9
+
+package context
+
+import "context" // standard library's context, as of Go 1.7
+
+// A Context carries a deadline, a cancelation signal, and other values across
+// API boundaries.
+//
+// Context's methods may be called by multiple goroutines simultaneously.
+type Context = context.Context
+
+// A CancelFunc tells an operation to abandon its work.
+// A CancelFunc does not wait for the work to stop.
+// After the first call, subsequent calls to a CancelFunc do nothing.
+type CancelFunc = context.CancelFunc
diff --git a/vendor/golang.org/x/net/context/pre_go17.go b/vendor/golang.org/x/net/context/pre_go17.go
new file mode 100644
index 0000000..0f35592
--- /dev/null
+++ b/vendor/golang.org/x/net/context/pre_go17.go
@@ -0,0 +1,300 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.7
+
+package context
+
+import (
+ "errors"
+ "fmt"
+ "sync"
+ "time"
+)
+
+// An emptyCtx is never canceled, has no values, and has no deadline. It is not
+// struct{}, since vars of this type must have distinct addresses.
+type emptyCtx int
+
+func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
+ return
+}
+
+func (*emptyCtx) Done() <-chan struct{} {
+ return nil
+}
+
+func (*emptyCtx) Err() error {
+ return nil
+}
+
+func (*emptyCtx) Value(key interface{}) interface{} {
+ return nil
+}
+
+func (e *emptyCtx) String() string {
+ switch e {
+ case background:
+ return "context.Background"
+ case todo:
+ return "context.TODO"
+ }
+ return "unknown empty Context"
+}
+
+var (
+ background = new(emptyCtx)
+ todo = new(emptyCtx)
+)
+
+// Canceled is the error returned by Context.Err when the context is canceled.
+var Canceled = errors.New("context canceled")
+
+// DeadlineExceeded is the error returned by Context.Err when the context's
+// deadline passes.
+var DeadlineExceeded = errors.New("context deadline exceeded")
+
+// WithCancel returns a copy of parent with a new Done channel. The returned
+// context's Done channel is closed when the returned cancel function is called
+// or when the parent context's Done channel is closed, whichever happens first.
+//
+// Canceling this context releases resources associated with it, so code should
+// call cancel as soon as the operations running in this Context complete.
+func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
+ c := newCancelCtx(parent)
+ propagateCancel(parent, c)
+ return c, func() { c.cancel(true, Canceled) }
+}
+
+// newCancelCtx returns an initialized cancelCtx.
+func newCancelCtx(parent Context) *cancelCtx {
+ return &cancelCtx{
+ Context: parent,
+ done: make(chan struct{}),
+ }
+}
+
+// propagateCancel arranges for child to be canceled when parent is.
+func propagateCancel(parent Context, child canceler) {
+ if parent.Done() == nil {
+ return // parent is never canceled
+ }
+ if p, ok := parentCancelCtx(parent); ok {
+ p.mu.Lock()
+ if p.err != nil {
+ // parent has already been canceled
+ child.cancel(false, p.err)
+ } else {
+ if p.children == nil {
+ p.children = make(map[canceler]bool)
+ }
+ p.children[child] = true
+ }
+ p.mu.Unlock()
+ } else {
+ go func() {
+ select {
+ case <-parent.Done():
+ child.cancel(false, parent.Err())
+ case <-child.Done():
+ }
+ }()
+ }
+}
+
+// parentCancelCtx follows a chain of parent references until it finds a
+// *cancelCtx. This function understands how each of the concrete types in this
+// package represents its parent.
+func parentCancelCtx(parent Context) (*cancelCtx, bool) {
+ for {
+ switch c := parent.(type) {
+ case *cancelCtx:
+ return c, true
+ case *timerCtx:
+ return c.cancelCtx, true
+ case *valueCtx:
+ parent = c.Context
+ default:
+ return nil, false
+ }
+ }
+}
+
+// removeChild removes a context from its parent.
+func removeChild(parent Context, child canceler) {
+ p, ok := parentCancelCtx(parent)
+ if !ok {
+ return
+ }
+ p.mu.Lock()
+ if p.children != nil {
+ delete(p.children, child)
+ }
+ p.mu.Unlock()
+}
+
+// A canceler is a context type that can be canceled directly. The
+// implementations are *cancelCtx and *timerCtx.
+type canceler interface {
+ cancel(removeFromParent bool, err error)
+ Done() <-chan struct{}
+}
+
+// A cancelCtx can be canceled. When canceled, it also cancels any children
+// that implement canceler.
+type cancelCtx struct {
+ Context
+
+ done chan struct{} // closed by the first cancel call.
+
+ mu sync.Mutex
+ children map[canceler]bool // set to nil by the first cancel call
+ err error // set to non-nil by the first cancel call
+}
+
+func (c *cancelCtx) Done() <-chan struct{} {
+ return c.done
+}
+
+func (c *cancelCtx) Err() error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ return c.err
+}
+
+func (c *cancelCtx) String() string {
+ return fmt.Sprintf("%v.WithCancel", c.Context)
+}
+
+// cancel closes c.done, cancels each of c's children, and, if
+// removeFromParent is true, removes c from its parent's children.
+func (c *cancelCtx) cancel(removeFromParent bool, err error) {
+ if err == nil {
+ panic("context: internal error: missing cancel error")
+ }
+ c.mu.Lock()
+ if c.err != nil {
+ c.mu.Unlock()
+ return // already canceled
+ }
+ c.err = err
+ close(c.done)
+ for child := range c.children {
+ // NOTE: acquiring the child's lock while holding parent's lock.
+ child.cancel(false, err)
+ }
+ c.children = nil
+ c.mu.Unlock()
+
+ if removeFromParent {
+ removeChild(c.Context, c)
+ }
+}
+
+// WithDeadline returns a copy of the parent context with the deadline adjusted
+// to be no later than d. If the parent's deadline is already earlier than d,
+// WithDeadline(parent, d) is semantically equivalent to parent. The returned
+// context's Done channel is closed when the deadline expires, when the returned
+// cancel function is called, or when the parent context's Done channel is
+// closed, whichever happens first.
+//
+// Canceling this context releases resources associated with it, so code should
+// call cancel as soon as the operations running in this Context complete.
+func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
+ if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
+ // The current deadline is already sooner than the new one.
+ return WithCancel(parent)
+ }
+ c := &timerCtx{
+ cancelCtx: newCancelCtx(parent),
+ deadline: deadline,
+ }
+ propagateCancel(parent, c)
+ d := deadline.Sub(time.Now())
+ if d <= 0 {
+ c.cancel(true, DeadlineExceeded) // deadline has already passed
+ return c, func() { c.cancel(true, Canceled) }
+ }
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if c.err == nil {
+ c.timer = time.AfterFunc(d, func() {
+ c.cancel(true, DeadlineExceeded)
+ })
+ }
+ return c, func() { c.cancel(true, Canceled) }
+}
+
+// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
+// implement Done and Err. It implements cancel by stopping its timer then
+// delegating to cancelCtx.cancel.
+type timerCtx struct {
+ *cancelCtx
+ timer *time.Timer // Under cancelCtx.mu.
+
+ deadline time.Time
+}
+
+func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
+ return c.deadline, true
+}
+
+func (c *timerCtx) String() string {
+ return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
+}
+
+func (c *timerCtx) cancel(removeFromParent bool, err error) {
+ c.cancelCtx.cancel(false, err)
+ if removeFromParent {
+ // Remove this timerCtx from its parent cancelCtx's children.
+ removeChild(c.cancelCtx.Context, c)
+ }
+ c.mu.Lock()
+ if c.timer != nil {
+ c.timer.Stop()
+ c.timer = nil
+ }
+ c.mu.Unlock()
+}
+
+// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
+//
+// Canceling this context releases resources associated with it, so code should
+// call cancel as soon as the operations running in this Context complete:
+//
+// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
+// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
+// defer cancel() // releases resources if slowOperation completes before timeout elapses
+// return slowOperation(ctx)
+// }
+func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
+ return WithDeadline(parent, time.Now().Add(timeout))
+}
+
+// WithValue returns a copy of parent in which the value associated with key is
+// val.
+//
+// Use context Values only for request-scoped data that transits processes and
+// APIs, not for passing optional parameters to functions.
+func WithValue(parent Context, key interface{}, val interface{}) Context {
+ return &valueCtx{parent, key, val}
+}
+
+// A valueCtx carries a key-value pair. It implements Value for that key and
+// delegates all other calls to the embedded Context.
+type valueCtx struct {
+ Context
+ key, val interface{}
+}
+
+func (c *valueCtx) String() string {
+ return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
+}
+
+func (c *valueCtx) Value(key interface{}) interface{} {
+ if c.key == key {
+ return c.val
+ }
+ return c.Context.Value(key)
+}
diff --git a/vendor/golang.org/x/net/context/pre_go19.go b/vendor/golang.org/x/net/context/pre_go19.go
new file mode 100644
index 0000000..b105f80
--- /dev/null
+++ b/vendor/golang.org/x/net/context/pre_go19.go
@@ -0,0 +1,109 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.9
+
+package context
+
+import "time"
+
+// A Context carries a deadline, a cancelation signal, and other values across
+// API boundaries.
+//
+// Context's methods may be called by multiple goroutines simultaneously.
+type Context interface {
+ // Deadline returns the time when work done on behalf of this context
+ // should be canceled. Deadline returns ok==false when no deadline is
+ // set. Successive calls to Deadline return the same results.
+ Deadline() (deadline time.Time, ok bool)
+
+ // Done returns a channel that's closed when work done on behalf of this
+ // context should be canceled. Done may return nil if this context can
+ // never be canceled. Successive calls to Done return the same value.
+ //
+ // WithCancel arranges for Done to be closed when cancel is called;
+ // WithDeadline arranges for Done to be closed when the deadline
+ // expires; WithTimeout arranges for Done to be closed when the timeout
+ // elapses.
+ //
+ // Done is provided for use in select statements:
+ //
+ // // Stream generates values with DoSomething and sends them to out
+ // // until DoSomething returns an error or ctx.Done is closed.
+ // func Stream(ctx context.Context, out chan<- Value) error {
+ // for {
+ // v, err := DoSomething(ctx)
+ // if err != nil {
+ // return err
+ // }
+ // select {
+ // case <-ctx.Done():
+ // return ctx.Err()
+ // case out <- v:
+ // }
+ // }
+ // }
+ //
+ // See http://blog.golang.org/pipelines for more examples of how to use
+ // a Done channel for cancelation.
+ Done() <-chan struct{}
+
+ // Err returns a non-nil error value after Done is closed. Err returns
+ // Canceled if the context was canceled or DeadlineExceeded if the
+ // context's deadline passed. No other values for Err are defined.
+ // After Done is closed, successive calls to Err return the same value.
+ Err() error
+
+ // Value returns the value associated with this context for key, or nil
+ // if no value is associated with key. Successive calls to Value with
+ // the same key returns the same result.
+ //
+ // Use context values only for request-scoped data that transits
+ // processes and API boundaries, not for passing optional parameters to
+ // functions.
+ //
+ // A key identifies a specific value in a Context. Functions that wish
+ // to store values in Context typically allocate a key in a global
+ // variable then use that key as the argument to context.WithValue and
+ // Context.Value. A key can be any type that supports equality;
+ // packages should define keys as an unexported type to avoid
+ // collisions.
+ //
+ // Packages that define a Context key should provide type-safe accessors
+ // for the values stores using that key:
+ //
+ // // Package user defines a User type that's stored in Contexts.
+ // package user
+ //
+ // import "golang.org/x/net/context"
+ //
+ // // User is the type of value stored in the Contexts.
+ // type User struct {...}
+ //
+ // // key is an unexported type for keys defined in this package.
+ // // This prevents collisions with keys defined in other packages.
+ // type key int
+ //
+ // // userKey is the key for user.User values in Contexts. It is
+ // // unexported; clients use user.NewContext and user.FromContext
+ // // instead of using this key directly.
+ // var userKey key = 0
+ //
+ // // NewContext returns a new Context that carries value u.
+ // func NewContext(ctx context.Context, u *User) context.Context {
+ // return context.WithValue(ctx, userKey, u)
+ // }
+ //
+ // // FromContext returns the User value stored in ctx, if any.
+ // func FromContext(ctx context.Context) (*User, bool) {
+ // u, ok := ctx.Value(userKey).(*User)
+ // return u, ok
+ // }
+ Value(key interface{}) interface{}
+}
+
+// A CancelFunc tells an operation to abandon its work.
+// A CancelFunc does not wait for the work to stop.
+// After the first call, subsequent calls to a CancelFunc do nothing.
+type CancelFunc func()
diff --git a/vendor/golang.org/x/net/context/withtimeout_test.go b/vendor/golang.org/x/net/context/withtimeout_test.go
new file mode 100644
index 0000000..e6f5669
--- /dev/null
+++ b/vendor/golang.org/x/net/context/withtimeout_test.go
@@ -0,0 +1,31 @@
+// Copyright 2014 The Go 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 context_test
+
+import (
+ "fmt"
+ "time"
+
+ "golang.org/x/net/context"
+)
+
+// This example passes a context with a timeout to tell a blocking function that
+// it should abandon its work after the timeout elapses.
+func ExampleWithTimeout() {
+ // Pass a context with a timeout to tell a blocking function that it
+ // should abandon its work after the timeout elapses.
+ ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
+ defer cancel()
+
+ select {
+ case <-time.After(1 * time.Second):
+ fmt.Println("overslept")
+ case <-ctx.Done():
+ fmt.Println(ctx.Err()) // prints "context deadline exceeded"
+ }
+
+ // Output:
+ // context deadline exceeded
+}
diff --git a/vendor/golang.org/x/net/dict/dict.go b/vendor/golang.org/x/net/dict/dict.go
new file mode 100644
index 0000000..93e65c0
--- /dev/null
+++ b/vendor/golang.org/x/net/dict/dict.go
@@ -0,0 +1,210 @@
+// Copyright 2010 The Go 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 dict implements the Dictionary Server Protocol
+// as defined in RFC 2229.
+package dict // import "golang.org/x/net/dict"
+
+import (
+ "net/textproto"
+ "strconv"
+ "strings"
+)
+
+// A Client represents a client connection to a dictionary server.
+type Client struct {
+ text *textproto.Conn
+}
+
+// Dial returns a new client connected to a dictionary server at
+// addr on the given network.
+func Dial(network, addr string) (*Client, error) {
+ text, err := textproto.Dial(network, addr)
+ if err != nil {
+ return nil, err
+ }
+ _, _, err = text.ReadCodeLine(220)
+ if err != nil {
+ text.Close()
+ return nil, err
+ }
+ return &Client{text: text}, nil
+}
+
+// Close closes the connection to the dictionary server.
+func (c *Client) Close() error {
+ return c.text.Close()
+}
+
+// A Dict represents a dictionary available on the server.
+type Dict struct {
+ Name string // short name of dictionary
+ Desc string // long description
+}
+
+// Dicts returns a list of the dictionaries available on the server.
+func (c *Client) Dicts() ([]Dict, error) {
+ id, err := c.text.Cmd("SHOW DB")
+ if err != nil {
+ return nil, err
+ }
+
+ c.text.StartResponse(id)
+ defer c.text.EndResponse(id)
+
+ _, _, err = c.text.ReadCodeLine(110)
+ if err != nil {
+ return nil, err
+ }
+ lines, err := c.text.ReadDotLines()
+ if err != nil {
+ return nil, err
+ }
+ _, _, err = c.text.ReadCodeLine(250)
+
+ dicts := make([]Dict, len(lines))
+ for i := range dicts {
+ d := &dicts[i]
+ a, _ := fields(lines[i])
+ if len(a) < 2 {
+ return nil, textproto.ProtocolError("invalid dictionary: " + lines[i])
+ }
+ d.Name = a[0]
+ d.Desc = a[1]
+ }
+ return dicts, err
+}
+
+// A Defn represents a definition.
+type Defn struct {
+ Dict Dict // Dict where definition was found
+ Word string // Word being defined
+ Text []byte // Definition text, typically multiple lines
+}
+
+// Define requests the definition of the given word.
+// The argument dict names the dictionary to use,
+// the Name field of a Dict returned by Dicts.
+//
+// The special dictionary name "*" means to look in all the
+// server's dictionaries.
+// The special dictionary name "!" means to look in all the
+// server's dictionaries in turn, stopping after finding the word
+// in one of them.
+func (c *Client) Define(dict, word string) ([]*Defn, error) {
+ id, err := c.text.Cmd("DEFINE %s %q", dict, word)
+ if err != nil {
+ return nil, err
+ }
+
+ c.text.StartResponse(id)
+ defer c.text.EndResponse(id)
+
+ _, line, err := c.text.ReadCodeLine(150)
+ if err != nil {
+ return nil, err
+ }
+ a, _ := fields(line)
+ if len(a) < 1 {
+ return nil, textproto.ProtocolError("malformed response: " + line)
+ }
+ n, err := strconv.Atoi(a[0])
+ if err != nil {
+ return nil, textproto.ProtocolError("invalid definition count: " + a[0])
+ }
+ def := make([]*Defn, n)
+ for i := 0; i < n; i++ {
+ _, line, err = c.text.ReadCodeLine(151)
+ if err != nil {
+ return nil, err
+ }
+ a, _ := fields(line)
+ if len(a) < 3 {
+ // skip it, to keep protocol in sync
+ i--
+ n--
+ def = def[0:n]
+ continue
+ }
+ d := &Defn{Word: a[0], Dict: Dict{a[1], a[2]}}
+ d.Text, err = c.text.ReadDotBytes()
+ if err != nil {
+ return nil, err
+ }
+ def[i] = d
+ }
+ _, _, err = c.text.ReadCodeLine(250)
+ return def, err
+}
+
+// Fields returns the fields in s.
+// Fields are space separated unquoted words
+// or quoted with single or double quote.
+func fields(s string) ([]string, error) {
+ var v []string
+ i := 0
+ for {
+ for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
+ i++
+ }
+ if i >= len(s) {
+ break
+ }
+ if s[i] == '"' || s[i] == '\'' {
+ q := s[i]
+ // quoted string
+ var j int
+ for j = i + 1; ; j++ {
+ if j >= len(s) {
+ return nil, textproto.ProtocolError("malformed quoted string")
+ }
+ if s[j] == '\\' {
+ j++
+ continue
+ }
+ if s[j] == q {
+ j++
+ break
+ }
+ }
+ v = append(v, unquote(s[i+1:j-1]))
+ i = j
+ } else {
+ // atom
+ var j int
+ for j = i; j < len(s); j++ {
+ if s[j] == ' ' || s[j] == '\t' || s[j] == '\\' || s[j] == '"' || s[j] == '\'' {
+ break
+ }
+ }
+ v = append(v, s[i:j])
+ i = j
+ }
+ if i < len(s) {
+ c := s[i]
+ if c != ' ' && c != '\t' {
+ return nil, textproto.ProtocolError("quotes not on word boundaries")
+ }
+ }
+ }
+ return v, nil
+}
+
+func unquote(s string) string {
+ if strings.Index(s, "\\") < 0 {
+ return s
+ }
+ b := []byte(s)
+ w := 0
+ for r := 0; r < len(b); r++ {
+ c := b[r]
+ if c == '\\' {
+ r++
+ c = b[r]
+ }
+ b[w] = c
+ w++
+ }
+ return string(b[0:w])
+}
diff --git a/vendor/golang.org/x/net/dns/dnsmessage/example_test.go b/vendor/golang.org/x/net/dns/dnsmessage/example_test.go
new file mode 100644
index 0000000..8600a6b
--- /dev/null
+++ b/vendor/golang.org/x/net/dns/dnsmessage/example_test.go
@@ -0,0 +1,132 @@
+// Copyright 2017 The Go 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 dnsmessage_test
+
+import (
+ "fmt"
+ "net"
+ "strings"
+
+ "golang.org/x/net/dns/dnsmessage"
+)
+
+func mustNewName(name string) dnsmessage.Name {
+ n, err := dnsmessage.NewName(name)
+ if err != nil {
+ panic(err)
+ }
+ return n
+}
+
+func ExampleParser() {
+ msg := dnsmessage.Message{
+ Header: dnsmessage.Header{Response: true, Authoritative: true},
+ Questions: []dnsmessage.Question{
+ {
+ Name: mustNewName("foo.bar.example.com."),
+ Type: dnsmessage.TypeA,
+ Class: dnsmessage.ClassINET,
+ },
+ {
+ Name: mustNewName("bar.example.com."),
+ Type: dnsmessage.TypeA,
+ Class: dnsmessage.ClassINET,
+ },
+ },
+ Answers: []dnsmessage.Resource{
+ {
+ Header: dnsmessage.ResourceHeader{
+ Name: mustNewName("foo.bar.example.com."),
+ Type: dnsmessage.TypeA,
+ Class: dnsmessage.ClassINET,
+ },
+ Body: &dnsmessage.AResource{A: [4]byte{127, 0, 0, 1}},
+ },
+ {
+ Header: dnsmessage.ResourceHeader{
+ Name: mustNewName("bar.example.com."),
+ Type: dnsmessage.TypeA,
+ Class: dnsmessage.ClassINET,
+ },
+ Body: &dnsmessage.AResource{A: [4]byte{127, 0, 0, 2}},
+ },
+ },
+ }
+
+ buf, err := msg.Pack()
+ if err != nil {
+ panic(err)
+ }
+
+ wantName := "bar.example.com."
+
+ var p dnsmessage.Parser
+ if _, err := p.Start(buf); err != nil {
+ panic(err)
+ }
+
+ for {
+ q, err := p.Question()
+ if err == dnsmessage.ErrSectionDone {
+ break
+ }
+ if err != nil {
+ panic(err)
+ }
+
+ if q.Name.String() != wantName {
+ continue
+ }
+
+ fmt.Println("Found question for name", wantName)
+ if err := p.SkipAllQuestions(); err != nil {
+ panic(err)
+ }
+ break
+ }
+
+ var gotIPs []net.IP
+ for {
+ h, err := p.AnswerHeader()
+ if err == dnsmessage.ErrSectionDone {
+ break
+ }
+ if err != nil {
+ panic(err)
+ }
+
+ if (h.Type != dnsmessage.TypeA && h.Type != dnsmessage.TypeAAAA) || h.Class != dnsmessage.ClassINET {
+ continue
+ }
+
+ if !strings.EqualFold(h.Name.String(), wantName) {
+ if err := p.SkipAnswer(); err != nil {
+ panic(err)
+ }
+ continue
+ }
+
+ switch h.Type {
+ case dnsmessage.TypeA:
+ r, err := p.AResource()
+ if err != nil {
+ panic(err)
+ }
+ gotIPs = append(gotIPs, r.A[:])
+ case dnsmessage.TypeAAAA:
+ r, err := p.AAAAResource()
+ if err != nil {
+ panic(err)
+ }
+ gotIPs = append(gotIPs, r.AAAA[:])
+ }
+ }
+
+ fmt.Printf("Found A/AAAA records for name %s: %v\n", wantName, gotIPs)
+
+ // Output:
+ // Found question for name bar.example.com.
+ // Found A/AAAA records for name bar.example.com.: [127.0.0.2]
+}
diff --git a/vendor/golang.org/x/net/dns/dnsmessage/message.go b/vendor/golang.org/x/net/dns/dnsmessage/message.go
new file mode 100644
index 0000000..38f8177
--- /dev/null
+++ b/vendor/golang.org/x/net/dns/dnsmessage/message.go
@@ -0,0 +1,2247 @@
+// Copyright 2009 The Go 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 dnsmessage provides a mostly RFC 1035 compliant implementation of
+// DNS message packing and unpacking.
+//
+// The package also supports messages with Extension Mechanisms for DNS
+// (EDNS(0)) as defined in RFC 6891.
+//
+// This implementation is designed to minimize heap allocations and avoid
+// unnecessary packing and unpacking as much as possible.
+package dnsmessage
+
+import (
+ "errors"
+)
+
+// Message formats
+
+// A Type is a type of DNS request and response.
+type Type uint16
+
+// A Class is a type of network.
+type Class uint16
+
+// An OpCode is a DNS operation code.
+type OpCode uint16
+
+// An RCode is a DNS response status code.
+type RCode uint16
+
+// Wire constants.
+const (
+ // ResourceHeader.Type and Question.Type
+ TypeA Type = 1
+ TypeNS Type = 2
+ TypeCNAME Type = 5
+ TypeSOA Type = 6
+ TypePTR Type = 12
+ TypeMX Type = 15
+ TypeTXT Type = 16
+ TypeAAAA Type = 28
+ TypeSRV Type = 33
+ TypeOPT Type = 41
+
+ // Question.Type
+ TypeWKS Type = 11
+ TypeHINFO Type = 13
+ TypeMINFO Type = 14
+ TypeAXFR Type = 252
+ TypeALL Type = 255
+
+ // ResourceHeader.Class and Question.Class
+ ClassINET Class = 1
+ ClassCSNET Class = 2
+ ClassCHAOS Class = 3
+ ClassHESIOD Class = 4
+
+ // Question.Class
+ ClassANY Class = 255
+
+ // Message.Rcode
+ RCodeSuccess RCode = 0
+ RCodeFormatError RCode = 1
+ RCodeServerFailure RCode = 2
+ RCodeNameError RCode = 3
+ RCodeNotImplemented RCode = 4
+ RCodeRefused RCode = 5
+)
+
+var (
+ // ErrNotStarted indicates that the prerequisite information isn't
+ // available yet because the previous records haven't been appropriately
+ // parsed, skipped or finished.
+ ErrNotStarted = errors.New("parsing/packing of this type isn't available yet")
+
+ // ErrSectionDone indicated that all records in the section have been
+ // parsed or finished.
+ ErrSectionDone = errors.New("parsing/packing of this section has completed")
+
+ errBaseLen = errors.New("insufficient data for base length type")
+ errCalcLen = errors.New("insufficient data for calculated length type")
+ errReserved = errors.New("segment prefix is reserved")
+ errTooManyPtr = errors.New("too many pointers (>10)")
+ errInvalidPtr = errors.New("invalid pointer")
+ errNilResouceBody = errors.New("nil resource body")
+ errResourceLen = errors.New("insufficient data for resource body length")
+ errSegTooLong = errors.New("segment length too long")
+ errZeroSegLen = errors.New("zero length segment")
+ errResTooLong = errors.New("resource length too long")
+ errTooManyQuestions = errors.New("too many Questions to pack (>65535)")
+ errTooManyAnswers = errors.New("too many Answers to pack (>65535)")
+ errTooManyAuthorities = errors.New("too many Authorities to pack (>65535)")
+ errTooManyAdditionals = errors.New("too many Additionals to pack (>65535)")
+ errNonCanonicalName = errors.New("name is not in canonical format (it must end with a .)")
+ errStringTooLong = errors.New("character string exceeds maximum length (255)")
+ errCompressedSRV = errors.New("compressed name in SRV resource data")
+)
+
+// Internal constants.
+const (
+ // packStartingCap is the default initial buffer size allocated during
+ // packing.
+ //
+ // The starting capacity doesn't matter too much, but most DNS responses
+ // Will be <= 512 bytes as it is the limit for DNS over UDP.
+ packStartingCap = 512
+
+ // uint16Len is the length (in bytes) of a uint16.
+ uint16Len = 2
+
+ // uint32Len is the length (in bytes) of a uint32.
+ uint32Len = 4
+
+ // headerLen is the length (in bytes) of a DNS header.
+ //
+ // A header is comprised of 6 uint16s and no padding.
+ headerLen = 6 * uint16Len
+)
+
+type nestedError struct {
+ // s is the current level's error message.
+ s string
+
+ // err is the nested error.
+ err error
+}
+
+// nestedError implements error.Error.
+func (e *nestedError) Error() string {
+ return e.s + ": " + e.err.Error()
+}
+
+// Header is a representation of a DNS message header.
+type Header struct {
+ ID uint16
+ Response bool
+ OpCode OpCode
+ Authoritative bool
+ Truncated bool
+ RecursionDesired bool
+ RecursionAvailable bool
+ RCode RCode
+}
+
+func (m *Header) pack() (id uint16, bits uint16) {
+ id = m.ID
+ bits = uint16(m.OpCode)<<11 | uint16(m.RCode)
+ if m.RecursionAvailable {
+ bits |= headerBitRA
+ }
+ if m.RecursionDesired {
+ bits |= headerBitRD
+ }
+ if m.Truncated {
+ bits |= headerBitTC
+ }
+ if m.Authoritative {
+ bits |= headerBitAA
+ }
+ if m.Response {
+ bits |= headerBitQR
+ }
+ return
+}
+
+// Message is a representation of a DNS message.
+type Message struct {
+ Header
+ Questions []Question
+ Answers []Resource
+ Authorities []Resource
+ Additionals []Resource
+}
+
+type section uint8
+
+const (
+ sectionNotStarted section = iota
+ sectionHeader
+ sectionQuestions
+ sectionAnswers
+ sectionAuthorities
+ sectionAdditionals
+ sectionDone
+
+ headerBitQR = 1 << 15 // query/response (response=1)
+ headerBitAA = 1 << 10 // authoritative
+ headerBitTC = 1 << 9 // truncated
+ headerBitRD = 1 << 8 // recursion desired
+ headerBitRA = 1 << 7 // recursion available
+)
+
+var sectionNames = map[section]string{
+ sectionHeader: "header",
+ sectionQuestions: "Question",
+ sectionAnswers: "Answer",
+ sectionAuthorities: "Authority",
+ sectionAdditionals: "Additional",
+}
+
+// header is the wire format for a DNS message header.
+type header struct {
+ id uint16
+ bits uint16
+ questions uint16
+ answers uint16
+ authorities uint16
+ additionals uint16
+}
+
+func (h *header) count(sec section) uint16 {
+ switch sec {
+ case sectionQuestions:
+ return h.questions
+ case sectionAnswers:
+ return h.answers
+ case sectionAuthorities:
+ return h.authorities
+ case sectionAdditionals:
+ return h.additionals
+ }
+ return 0
+}
+
+// pack appends the wire format of the header to msg.
+func (h *header) pack(msg []byte) []byte {
+ msg = packUint16(msg, h.id)
+ msg = packUint16(msg, h.bits)
+ msg = packUint16(msg, h.questions)
+ msg = packUint16(msg, h.answers)
+ msg = packUint16(msg, h.authorities)
+ return packUint16(msg, h.additionals)
+}
+
+func (h *header) unpack(msg []byte, off int) (int, error) {
+ newOff := off
+ var err error
+ if h.id, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"id", err}
+ }
+ if h.bits, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"bits", err}
+ }
+ if h.questions, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"questions", err}
+ }
+ if h.answers, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"answers", err}
+ }
+ if h.authorities, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"authorities", err}
+ }
+ if h.additionals, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"additionals", err}
+ }
+ return newOff, nil
+}
+
+func (h *header) header() Header {
+ return Header{
+ ID: h.id,
+ Response: (h.bits & headerBitQR) != 0,
+ OpCode: OpCode(h.bits>>11) & 0xF,
+ Authoritative: (h.bits & headerBitAA) != 0,
+ Truncated: (h.bits & headerBitTC) != 0,
+ RecursionDesired: (h.bits & headerBitRD) != 0,
+ RecursionAvailable: (h.bits & headerBitRA) != 0,
+ RCode: RCode(h.bits & 0xF),
+ }
+}
+
+// A Resource is a DNS resource record.
+type Resource struct {
+ Header ResourceHeader
+ Body ResourceBody
+}
+
+// A ResourceBody is a DNS resource record minus the header.
+type ResourceBody interface {
+ // pack packs a Resource except for its header.
+ pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error)
+
+ // realType returns the actual type of the Resource. This is used to
+ // fill in the header Type field.
+ realType() Type
+}
+
+// pack appends the wire format of the Resource to msg.
+func (r *Resource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ if r.Body == nil {
+ return msg, errNilResouceBody
+ }
+ oldMsg := msg
+ r.Header.Type = r.Body.realType()
+ msg, length, err := r.Header.pack(msg, compression, compressionOff)
+ if err != nil {
+ return msg, &nestedError{"ResourceHeader", err}
+ }
+ preLen := len(msg)
+ msg, err = r.Body.pack(msg, compression, compressionOff)
+ if err != nil {
+ return msg, &nestedError{"content", err}
+ }
+ if err := r.Header.fixLen(msg, length, preLen); err != nil {
+ return oldMsg, err
+ }
+ return msg, nil
+}
+
+// A Parser allows incrementally parsing a DNS message.
+//
+// When parsing is started, the Header is parsed. Next, each Question can be
+// either parsed or skipped. Alternatively, all Questions can be skipped at
+// once. When all Questions have been parsed, attempting to parse Questions
+// will return (nil, nil) and attempting to skip Questions will return
+// (true, nil). After all Questions have been either parsed or skipped, all
+// Answers, Authorities and Additionals can be either parsed or skipped in the
+// same way, and each type of Resource must be fully parsed or skipped before
+// proceeding to the next type of Resource.
+//
+// Note that there is no requirement to fully skip or parse the message.
+type Parser struct {
+ msg []byte
+ header header
+
+ section section
+ off int
+ index int
+ resHeaderValid bool
+ resHeader ResourceHeader
+}
+
+// Start parses the header and enables the parsing of Questions.
+func (p *Parser) Start(msg []byte) (Header, error) {
+ if p.msg != nil {
+ *p = Parser{}
+ }
+ p.msg = msg
+ var err error
+ if p.off, err = p.header.unpack(msg, 0); err != nil {
+ return Header{}, &nestedError{"unpacking header", err}
+ }
+ p.section = sectionQuestions
+ return p.header.header(), nil
+}
+
+func (p *Parser) checkAdvance(sec section) error {
+ if p.section < sec {
+ return ErrNotStarted
+ }
+ if p.section > sec {
+ return ErrSectionDone
+ }
+ p.resHeaderValid = false
+ if p.index == int(p.header.count(sec)) {
+ p.index = 0
+ p.section++
+ return ErrSectionDone
+ }
+ return nil
+}
+
+func (p *Parser) resource(sec section) (Resource, error) {
+ var r Resource
+ var err error
+ r.Header, err = p.resourceHeader(sec)
+ if err != nil {
+ return r, err
+ }
+ p.resHeaderValid = false
+ r.Body, p.off, err = unpackResourceBody(p.msg, p.off, r.Header)
+ if err != nil {
+ return Resource{}, &nestedError{"unpacking " + sectionNames[sec], err}
+ }
+ p.index++
+ return r, nil
+}
+
+func (p *Parser) resourceHeader(sec section) (ResourceHeader, error) {
+ if p.resHeaderValid {
+ return p.resHeader, nil
+ }
+ if err := p.checkAdvance(sec); err != nil {
+ return ResourceHeader{}, err
+ }
+ var hdr ResourceHeader
+ off, err := hdr.unpack(p.msg, p.off)
+ if err != nil {
+ return ResourceHeader{}, err
+ }
+ p.resHeaderValid = true
+ p.resHeader = hdr
+ p.off = off
+ return hdr, nil
+}
+
+func (p *Parser) skipResource(sec section) error {
+ if p.resHeaderValid {
+ newOff := p.off + int(p.resHeader.Length)
+ if newOff > len(p.msg) {
+ return errResourceLen
+ }
+ p.off = newOff
+ p.resHeaderValid = false
+ p.index++
+ return nil
+ }
+ if err := p.checkAdvance(sec); err != nil {
+ return err
+ }
+ var err error
+ p.off, err = skipResource(p.msg, p.off)
+ if err != nil {
+ return &nestedError{"skipping: " + sectionNames[sec], err}
+ }
+ p.index++
+ return nil
+}
+
+// Question parses a single Question.
+func (p *Parser) Question() (Question, error) {
+ if err := p.checkAdvance(sectionQuestions); err != nil {
+ return Question{}, err
+ }
+ var name Name
+ off, err := name.unpack(p.msg, p.off)
+ if err != nil {
+ return Question{}, &nestedError{"unpacking Question.Name", err}
+ }
+ typ, off, err := unpackType(p.msg, off)
+ if err != nil {
+ return Question{}, &nestedError{"unpacking Question.Type", err}
+ }
+ class, off, err := unpackClass(p.msg, off)
+ if err != nil {
+ return Question{}, &nestedError{"unpacking Question.Class", err}
+ }
+ p.off = off
+ p.index++
+ return Question{name, typ, class}, nil
+}
+
+// AllQuestions parses all Questions.
+func (p *Parser) AllQuestions() ([]Question, error) {
+ // Multiple questions are valid according to the spec,
+ // but servers don't actually support them. There will
+ // be at most one question here.
+ //
+ // Do not pre-allocate based on info in p.header, since
+ // the data is untrusted.
+ qs := []Question{}
+ for {
+ q, err := p.Question()
+ if err == ErrSectionDone {
+ return qs, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ qs = append(qs, q)
+ }
+}
+
+// SkipQuestion skips a single Question.
+func (p *Parser) SkipQuestion() error {
+ if err := p.checkAdvance(sectionQuestions); err != nil {
+ return err
+ }
+ off, err := skipName(p.msg, p.off)
+ if err != nil {
+ return &nestedError{"skipping Question Name", err}
+ }
+ if off, err = skipType(p.msg, off); err != nil {
+ return &nestedError{"skipping Question Type", err}
+ }
+ if off, err = skipClass(p.msg, off); err != nil {
+ return &nestedError{"skipping Question Class", err}
+ }
+ p.off = off
+ p.index++
+ return nil
+}
+
+// SkipAllQuestions skips all Questions.
+func (p *Parser) SkipAllQuestions() error {
+ for {
+ if err := p.SkipQuestion(); err == ErrSectionDone {
+ return nil
+ } else if err != nil {
+ return err
+ }
+ }
+}
+
+// AnswerHeader parses a single Answer ResourceHeader.
+func (p *Parser) AnswerHeader() (ResourceHeader, error) {
+ return p.resourceHeader(sectionAnswers)
+}
+
+// Answer parses a single Answer Resource.
+func (p *Parser) Answer() (Resource, error) {
+ return p.resource(sectionAnswers)
+}
+
+// AllAnswers parses all Answer Resources.
+func (p *Parser) AllAnswers() ([]Resource, error) {
+ // The most common query is for A/AAAA, which usually returns
+ // a handful of IPs.
+ //
+ // Pre-allocate up to a certain limit, since p.header is
+ // untrusted data.
+ n := int(p.header.answers)
+ if n > 20 {
+ n = 20
+ }
+ as := make([]Resource, 0, n)
+ for {
+ a, err := p.Answer()
+ if err == ErrSectionDone {
+ return as, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ as = append(as, a)
+ }
+}
+
+// SkipAnswer skips a single Answer Resource.
+func (p *Parser) SkipAnswer() error {
+ return p.skipResource(sectionAnswers)
+}
+
+// SkipAllAnswers skips all Answer Resources.
+func (p *Parser) SkipAllAnswers() error {
+ for {
+ if err := p.SkipAnswer(); err == ErrSectionDone {
+ return nil
+ } else if err != nil {
+ return err
+ }
+ }
+}
+
+// AuthorityHeader parses a single Authority ResourceHeader.
+func (p *Parser) AuthorityHeader() (ResourceHeader, error) {
+ return p.resourceHeader(sectionAuthorities)
+}
+
+// Authority parses a single Authority Resource.
+func (p *Parser) Authority() (Resource, error) {
+ return p.resource(sectionAuthorities)
+}
+
+// AllAuthorities parses all Authority Resources.
+func (p *Parser) AllAuthorities() ([]Resource, error) {
+ // Authorities contains SOA in case of NXDOMAIN and friends,
+ // otherwise it is empty.
+ //
+ // Pre-allocate up to a certain limit, since p.header is
+ // untrusted data.
+ n := int(p.header.authorities)
+ if n > 10 {
+ n = 10
+ }
+ as := make([]Resource, 0, n)
+ for {
+ a, err := p.Authority()
+ if err == ErrSectionDone {
+ return as, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ as = append(as, a)
+ }
+}
+
+// SkipAuthority skips a single Authority Resource.
+func (p *Parser) SkipAuthority() error {
+ return p.skipResource(sectionAuthorities)
+}
+
+// SkipAllAuthorities skips all Authority Resources.
+func (p *Parser) SkipAllAuthorities() error {
+ for {
+ if err := p.SkipAuthority(); err == ErrSectionDone {
+ return nil
+ } else if err != nil {
+ return err
+ }
+ }
+}
+
+// AdditionalHeader parses a single Additional ResourceHeader.
+func (p *Parser) AdditionalHeader() (ResourceHeader, error) {
+ return p.resourceHeader(sectionAdditionals)
+}
+
+// Additional parses a single Additional Resource.
+func (p *Parser) Additional() (Resource, error) {
+ return p.resource(sectionAdditionals)
+}
+
+// AllAdditionals parses all Additional Resources.
+func (p *Parser) AllAdditionals() ([]Resource, error) {
+ // Additionals usually contain OPT, and sometimes A/AAAA
+ // glue records.
+ //
+ // Pre-allocate up to a certain limit, since p.header is
+ // untrusted data.
+ n := int(p.header.additionals)
+ if n > 10 {
+ n = 10
+ }
+ as := make([]Resource, 0, n)
+ for {
+ a, err := p.Additional()
+ if err == ErrSectionDone {
+ return as, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ as = append(as, a)
+ }
+}
+
+// SkipAdditional skips a single Additional Resource.
+func (p *Parser) SkipAdditional() error {
+ return p.skipResource(sectionAdditionals)
+}
+
+// SkipAllAdditionals skips all Additional Resources.
+func (p *Parser) SkipAllAdditionals() error {
+ for {
+ if err := p.SkipAdditional(); err == ErrSectionDone {
+ return nil
+ } else if err != nil {
+ return err
+ }
+ }
+}
+
+// CNAMEResource parses a single CNAMEResource.
+//
+// One of the XXXHeader methods must have been called before calling this
+// method.
+func (p *Parser) CNAMEResource() (CNAMEResource, error) {
+ if !p.resHeaderValid || p.resHeader.Type != TypeCNAME {
+ return CNAMEResource{}, ErrNotStarted
+ }
+ r, err := unpackCNAMEResource(p.msg, p.off)
+ if err != nil {
+ return CNAMEResource{}, err
+ }
+ p.off += int(p.resHeader.Length)
+ p.resHeaderValid = false
+ p.index++
+ return r, nil
+}
+
+// MXResource parses a single MXResource.
+//
+// One of the XXXHeader methods must have been called before calling this
+// method.
+func (p *Parser) MXResource() (MXResource, error) {
+ if !p.resHeaderValid || p.resHeader.Type != TypeMX {
+ return MXResource{}, ErrNotStarted
+ }
+ r, err := unpackMXResource(p.msg, p.off)
+ if err != nil {
+ return MXResource{}, err
+ }
+ p.off += int(p.resHeader.Length)
+ p.resHeaderValid = false
+ p.index++
+ return r, nil
+}
+
+// NSResource parses a single NSResource.
+//
+// One of the XXXHeader methods must have been called before calling this
+// method.
+func (p *Parser) NSResource() (NSResource, error) {
+ if !p.resHeaderValid || p.resHeader.Type != TypeNS {
+ return NSResource{}, ErrNotStarted
+ }
+ r, err := unpackNSResource(p.msg, p.off)
+ if err != nil {
+ return NSResource{}, err
+ }
+ p.off += int(p.resHeader.Length)
+ p.resHeaderValid = false
+ p.index++
+ return r, nil
+}
+
+// PTRResource parses a single PTRResource.
+//
+// One of the XXXHeader methods must have been called before calling this
+// method.
+func (p *Parser) PTRResource() (PTRResource, error) {
+ if !p.resHeaderValid || p.resHeader.Type != TypePTR {
+ return PTRResource{}, ErrNotStarted
+ }
+ r, err := unpackPTRResource(p.msg, p.off)
+ if err != nil {
+ return PTRResource{}, err
+ }
+ p.off += int(p.resHeader.Length)
+ p.resHeaderValid = false
+ p.index++
+ return r, nil
+}
+
+// SOAResource parses a single SOAResource.
+//
+// One of the XXXHeader methods must have been called before calling this
+// method.
+func (p *Parser) SOAResource() (SOAResource, error) {
+ if !p.resHeaderValid || p.resHeader.Type != TypeSOA {
+ return SOAResource{}, ErrNotStarted
+ }
+ r, err := unpackSOAResource(p.msg, p.off)
+ if err != nil {
+ return SOAResource{}, err
+ }
+ p.off += int(p.resHeader.Length)
+ p.resHeaderValid = false
+ p.index++
+ return r, nil
+}
+
+// TXTResource parses a single TXTResource.
+//
+// One of the XXXHeader methods must have been called before calling this
+// method.
+func (p *Parser) TXTResource() (TXTResource, error) {
+ if !p.resHeaderValid || p.resHeader.Type != TypeTXT {
+ return TXTResource{}, ErrNotStarted
+ }
+ r, err := unpackTXTResource(p.msg, p.off, p.resHeader.Length)
+ if err != nil {
+ return TXTResource{}, err
+ }
+ p.off += int(p.resHeader.Length)
+ p.resHeaderValid = false
+ p.index++
+ return r, nil
+}
+
+// SRVResource parses a single SRVResource.
+//
+// One of the XXXHeader methods must have been called before calling this
+// method.
+func (p *Parser) SRVResource() (SRVResource, error) {
+ if !p.resHeaderValid || p.resHeader.Type != TypeSRV {
+ return SRVResource{}, ErrNotStarted
+ }
+ r, err := unpackSRVResource(p.msg, p.off)
+ if err != nil {
+ return SRVResource{}, err
+ }
+ p.off += int(p.resHeader.Length)
+ p.resHeaderValid = false
+ p.index++
+ return r, nil
+}
+
+// AResource parses a single AResource.
+//
+// One of the XXXHeader methods must have been called before calling this
+// method.
+func (p *Parser) AResource() (AResource, error) {
+ if !p.resHeaderValid || p.resHeader.Type != TypeA {
+ return AResource{}, ErrNotStarted
+ }
+ r, err := unpackAResource(p.msg, p.off)
+ if err != nil {
+ return AResource{}, err
+ }
+ p.off += int(p.resHeader.Length)
+ p.resHeaderValid = false
+ p.index++
+ return r, nil
+}
+
+// AAAAResource parses a single AAAAResource.
+//
+// One of the XXXHeader methods must have been called before calling this
+// method.
+func (p *Parser) AAAAResource() (AAAAResource, error) {
+ if !p.resHeaderValid || p.resHeader.Type != TypeAAAA {
+ return AAAAResource{}, ErrNotStarted
+ }
+ r, err := unpackAAAAResource(p.msg, p.off)
+ if err != nil {
+ return AAAAResource{}, err
+ }
+ p.off += int(p.resHeader.Length)
+ p.resHeaderValid = false
+ p.index++
+ return r, nil
+}
+
+// OPTResource parses a single OPTResource.
+//
+// One of the XXXHeader methods must have been called before calling this
+// method.
+func (p *Parser) OPTResource() (OPTResource, error) {
+ if !p.resHeaderValid || p.resHeader.Type != TypeOPT {
+ return OPTResource{}, ErrNotStarted
+ }
+ r, err := unpackOPTResource(p.msg, p.off, p.resHeader.Length)
+ if err != nil {
+ return OPTResource{}, err
+ }
+ p.off += int(p.resHeader.Length)
+ p.resHeaderValid = false
+ p.index++
+ return r, nil
+}
+
+// Unpack parses a full Message.
+func (m *Message) Unpack(msg []byte) error {
+ var p Parser
+ var err error
+ if m.Header, err = p.Start(msg); err != nil {
+ return err
+ }
+ if m.Questions, err = p.AllQuestions(); err != nil {
+ return err
+ }
+ if m.Answers, err = p.AllAnswers(); err != nil {
+ return err
+ }
+ if m.Authorities, err = p.AllAuthorities(); err != nil {
+ return err
+ }
+ if m.Additionals, err = p.AllAdditionals(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Pack packs a full Message.
+func (m *Message) Pack() ([]byte, error) {
+ return m.AppendPack(make([]byte, 0, packStartingCap))
+}
+
+// AppendPack is like Pack but appends the full Message to b and returns the
+// extended buffer.
+func (m *Message) AppendPack(b []byte) ([]byte, error) {
+ // Validate the lengths. It is very unlikely that anyone will try to
+ // pack more than 65535 of any particular type, but it is possible and
+ // we should fail gracefully.
+ if len(m.Questions) > int(^uint16(0)) {
+ return nil, errTooManyQuestions
+ }
+ if len(m.Answers) > int(^uint16(0)) {
+ return nil, errTooManyAnswers
+ }
+ if len(m.Authorities) > int(^uint16(0)) {
+ return nil, errTooManyAuthorities
+ }
+ if len(m.Additionals) > int(^uint16(0)) {
+ return nil, errTooManyAdditionals
+ }
+
+ var h header
+ h.id, h.bits = m.Header.pack()
+
+ h.questions = uint16(len(m.Questions))
+ h.answers = uint16(len(m.Answers))
+ h.authorities = uint16(len(m.Authorities))
+ h.additionals = uint16(len(m.Additionals))
+
+ compressionOff := len(b)
+ msg := h.pack(b)
+
+ // RFC 1035 allows (but does not require) compression for packing. RFC
+ // 1035 requires unpacking implementations to support compression, so
+ // unconditionally enabling it is fine.
+ //
+ // DNS lookups are typically done over UDP, and RFC 1035 states that UDP
+ // DNS messages can be a maximum of 512 bytes long. Without compression,
+ // many DNS response messages are over this limit, so enabling
+ // compression will help ensure compliance.
+ compression := map[string]int{}
+
+ for i := range m.Questions {
+ var err error
+ if msg, err = m.Questions[i].pack(msg, compression, compressionOff); err != nil {
+ return nil, &nestedError{"packing Question", err}
+ }
+ }
+ for i := range m.Answers {
+ var err error
+ if msg, err = m.Answers[i].pack(msg, compression, compressionOff); err != nil {
+ return nil, &nestedError{"packing Answer", err}
+ }
+ }
+ for i := range m.Authorities {
+ var err error
+ if msg, err = m.Authorities[i].pack(msg, compression, compressionOff); err != nil {
+ return nil, &nestedError{"packing Authority", err}
+ }
+ }
+ for i := range m.Additionals {
+ var err error
+ if msg, err = m.Additionals[i].pack(msg, compression, compressionOff); err != nil {
+ return nil, &nestedError{"packing Additional", err}
+ }
+ }
+
+ return msg, nil
+}
+
+// A Builder allows incrementally packing a DNS message.
+//
+// Example usage:
+// buf := make([]byte, 2, 514)
+// b := NewBuilder(buf, Header{...})
+// b.EnableCompression()
+// // Optionally start a section and add things to that section.
+// // Repeat adding sections as necessary.
+// buf, err := b.Finish()
+// // If err is nil, buf[2:] will contain the built bytes.
+type Builder struct {
+ // msg is the storage for the message being built.
+ msg []byte
+
+ // section keeps track of the current section being built.
+ section section
+
+ // header keeps track of what should go in the header when Finish is
+ // called.
+ header header
+
+ // start is the starting index of the bytes allocated in msg for header.
+ start int
+
+ // compression is a mapping from name suffixes to their starting index
+ // in msg.
+ compression map[string]int
+}
+
+// NewBuilder creates a new builder with compression disabled.
+//
+// Note: Most users will want to immediately enable compression with the
+// EnableCompression method. See that method's comment for why you may or may
+// not want to enable compression.
+//
+// The DNS message is appended to the provided initial buffer buf (which may be
+// nil) as it is built. The final message is returned by the (*Builder).Finish
+// method, which may return the same underlying array if there was sufficient
+// capacity in the slice.
+func NewBuilder(buf []byte, h Header) Builder {
+ if buf == nil {
+ buf = make([]byte, 0, packStartingCap)
+ }
+ b := Builder{msg: buf, start: len(buf)}
+ b.header.id, b.header.bits = h.pack()
+ var hb [headerLen]byte
+ b.msg = append(b.msg, hb[:]...)
+ b.section = sectionHeader
+ return b
+}
+
+// EnableCompression enables compression in the Builder.
+//
+// Leaving compression disabled avoids compression related allocations, but can
+// result in larger message sizes. Be careful with this mode as it can cause
+// messages to exceed the UDP size limit.
+//
+// According to RFC 1035, section 4.1.4, the use of compression is optional, but
+// all implementations must accept both compressed and uncompressed DNS
+// messages.
+//
+// Compression should be enabled before any sections are added for best results.
+func (b *Builder) EnableCompression() {
+ b.compression = map[string]int{}
+}
+
+func (b *Builder) startCheck(s section) error {
+ if b.section <= sectionNotStarted {
+ return ErrNotStarted
+ }
+ if b.section > s {
+ return ErrSectionDone
+ }
+ return nil
+}
+
+// StartQuestions prepares the builder for packing Questions.
+func (b *Builder) StartQuestions() error {
+ if err := b.startCheck(sectionQuestions); err != nil {
+ return err
+ }
+ b.section = sectionQuestions
+ return nil
+}
+
+// StartAnswers prepares the builder for packing Answers.
+func (b *Builder) StartAnswers() error {
+ if err := b.startCheck(sectionAnswers); err != nil {
+ return err
+ }
+ b.section = sectionAnswers
+ return nil
+}
+
+// StartAuthorities prepares the builder for packing Authorities.
+func (b *Builder) StartAuthorities() error {
+ if err := b.startCheck(sectionAuthorities); err != nil {
+ return err
+ }
+ b.section = sectionAuthorities
+ return nil
+}
+
+// StartAdditionals prepares the builder for packing Additionals.
+func (b *Builder) StartAdditionals() error {
+ if err := b.startCheck(sectionAdditionals); err != nil {
+ return err
+ }
+ b.section = sectionAdditionals
+ return nil
+}
+
+func (b *Builder) incrementSectionCount() error {
+ var count *uint16
+ var err error
+ switch b.section {
+ case sectionQuestions:
+ count = &b.header.questions
+ err = errTooManyQuestions
+ case sectionAnswers:
+ count = &b.header.answers
+ err = errTooManyAnswers
+ case sectionAuthorities:
+ count = &b.header.authorities
+ err = errTooManyAuthorities
+ case sectionAdditionals:
+ count = &b.header.additionals
+ err = errTooManyAdditionals
+ }
+ if *count == ^uint16(0) {
+ return err
+ }
+ *count++
+ return nil
+}
+
+// Question adds a single Question.
+func (b *Builder) Question(q Question) error {
+ if b.section < sectionQuestions {
+ return ErrNotStarted
+ }
+ if b.section > sectionQuestions {
+ return ErrSectionDone
+ }
+ msg, err := q.pack(b.msg, b.compression, b.start)
+ if err != nil {
+ return err
+ }
+ if err := b.incrementSectionCount(); err != nil {
+ return err
+ }
+ b.msg = msg
+ return nil
+}
+
+func (b *Builder) checkResourceSection() error {
+ if b.section < sectionAnswers {
+ return ErrNotStarted
+ }
+ if b.section > sectionAdditionals {
+ return ErrSectionDone
+ }
+ return nil
+}
+
+// CNAMEResource adds a single CNAMEResource.
+func (b *Builder) CNAMEResource(h ResourceHeader, r CNAMEResource) error {
+ if err := b.checkResourceSection(); err != nil {
+ return err
+ }
+ h.Type = r.realType()
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
+ if err != nil {
+ return &nestedError{"ResourceHeader", err}
+ }
+ preLen := len(msg)
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
+ return &nestedError{"CNAMEResource body", err}
+ }
+ if err := h.fixLen(msg, length, preLen); err != nil {
+ return err
+ }
+ if err := b.incrementSectionCount(); err != nil {
+ return err
+ }
+ b.msg = msg
+ return nil
+}
+
+// MXResource adds a single MXResource.
+func (b *Builder) MXResource(h ResourceHeader, r MXResource) error {
+ if err := b.checkResourceSection(); err != nil {
+ return err
+ }
+ h.Type = r.realType()
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
+ if err != nil {
+ return &nestedError{"ResourceHeader", err}
+ }
+ preLen := len(msg)
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
+ return &nestedError{"MXResource body", err}
+ }
+ if err := h.fixLen(msg, length, preLen); err != nil {
+ return err
+ }
+ if err := b.incrementSectionCount(); err != nil {
+ return err
+ }
+ b.msg = msg
+ return nil
+}
+
+// NSResource adds a single NSResource.
+func (b *Builder) NSResource(h ResourceHeader, r NSResource) error {
+ if err := b.checkResourceSection(); err != nil {
+ return err
+ }
+ h.Type = r.realType()
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
+ if err != nil {
+ return &nestedError{"ResourceHeader", err}
+ }
+ preLen := len(msg)
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
+ return &nestedError{"NSResource body", err}
+ }
+ if err := h.fixLen(msg, length, preLen); err != nil {
+ return err
+ }
+ if err := b.incrementSectionCount(); err != nil {
+ return err
+ }
+ b.msg = msg
+ return nil
+}
+
+// PTRResource adds a single PTRResource.
+func (b *Builder) PTRResource(h ResourceHeader, r PTRResource) error {
+ if err := b.checkResourceSection(); err != nil {
+ return err
+ }
+ h.Type = r.realType()
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
+ if err != nil {
+ return &nestedError{"ResourceHeader", err}
+ }
+ preLen := len(msg)
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
+ return &nestedError{"PTRResource body", err}
+ }
+ if err := h.fixLen(msg, length, preLen); err != nil {
+ return err
+ }
+ if err := b.incrementSectionCount(); err != nil {
+ return err
+ }
+ b.msg = msg
+ return nil
+}
+
+// SOAResource adds a single SOAResource.
+func (b *Builder) SOAResource(h ResourceHeader, r SOAResource) error {
+ if err := b.checkResourceSection(); err != nil {
+ return err
+ }
+ h.Type = r.realType()
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
+ if err != nil {
+ return &nestedError{"ResourceHeader", err}
+ }
+ preLen := len(msg)
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
+ return &nestedError{"SOAResource body", err}
+ }
+ if err := h.fixLen(msg, length, preLen); err != nil {
+ return err
+ }
+ if err := b.incrementSectionCount(); err != nil {
+ return err
+ }
+ b.msg = msg
+ return nil
+}
+
+// TXTResource adds a single TXTResource.
+func (b *Builder) TXTResource(h ResourceHeader, r TXTResource) error {
+ if err := b.checkResourceSection(); err != nil {
+ return err
+ }
+ h.Type = r.realType()
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
+ if err != nil {
+ return &nestedError{"ResourceHeader", err}
+ }
+ preLen := len(msg)
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
+ return &nestedError{"TXTResource body", err}
+ }
+ if err := h.fixLen(msg, length, preLen); err != nil {
+ return err
+ }
+ if err := b.incrementSectionCount(); err != nil {
+ return err
+ }
+ b.msg = msg
+ return nil
+}
+
+// SRVResource adds a single SRVResource.
+func (b *Builder) SRVResource(h ResourceHeader, r SRVResource) error {
+ if err := b.checkResourceSection(); err != nil {
+ return err
+ }
+ h.Type = r.realType()
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
+ if err != nil {
+ return &nestedError{"ResourceHeader", err}
+ }
+ preLen := len(msg)
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
+ return &nestedError{"SRVResource body", err}
+ }
+ if err := h.fixLen(msg, length, preLen); err != nil {
+ return err
+ }
+ if err := b.incrementSectionCount(); err != nil {
+ return err
+ }
+ b.msg = msg
+ return nil
+}
+
+// AResource adds a single AResource.
+func (b *Builder) AResource(h ResourceHeader, r AResource) error {
+ if err := b.checkResourceSection(); err != nil {
+ return err
+ }
+ h.Type = r.realType()
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
+ if err != nil {
+ return &nestedError{"ResourceHeader", err}
+ }
+ preLen := len(msg)
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
+ return &nestedError{"AResource body", err}
+ }
+ if err := h.fixLen(msg, length, preLen); err != nil {
+ return err
+ }
+ if err := b.incrementSectionCount(); err != nil {
+ return err
+ }
+ b.msg = msg
+ return nil
+}
+
+// AAAAResource adds a single AAAAResource.
+func (b *Builder) AAAAResource(h ResourceHeader, r AAAAResource) error {
+ if err := b.checkResourceSection(); err != nil {
+ return err
+ }
+ h.Type = r.realType()
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
+ if err != nil {
+ return &nestedError{"ResourceHeader", err}
+ }
+ preLen := len(msg)
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
+ return &nestedError{"AAAAResource body", err}
+ }
+ if err := h.fixLen(msg, length, preLen); err != nil {
+ return err
+ }
+ if err := b.incrementSectionCount(); err != nil {
+ return err
+ }
+ b.msg = msg
+ return nil
+}
+
+// OPTResource adds a single OPTResource.
+func (b *Builder) OPTResource(h ResourceHeader, r OPTResource) error {
+ if err := b.checkResourceSection(); err != nil {
+ return err
+ }
+ h.Type = r.realType()
+ msg, length, err := h.pack(b.msg, b.compression, b.start)
+ if err != nil {
+ return &nestedError{"ResourceHeader", err}
+ }
+ preLen := len(msg)
+ if msg, err = r.pack(msg, b.compression, b.start); err != nil {
+ return &nestedError{"OPTResource body", err}
+ }
+ if err := h.fixLen(msg, length, preLen); err != nil {
+ return err
+ }
+ if err := b.incrementSectionCount(); err != nil {
+ return err
+ }
+ b.msg = msg
+ return nil
+}
+
+// Finish ends message building and generates a binary message.
+func (b *Builder) Finish() ([]byte, error) {
+ if b.section < sectionHeader {
+ return nil, ErrNotStarted
+ }
+ b.section = sectionDone
+ // Space for the header was allocated in NewBuilder.
+ b.header.pack(b.msg[b.start:b.start])
+ return b.msg, nil
+}
+
+// A ResourceHeader is the header of a DNS resource record. There are
+// many types of DNS resource records, but they all share the same header.
+type ResourceHeader struct {
+ // Name is the domain name for which this resource record pertains.
+ Name Name
+
+ // Type is the type of DNS resource record.
+ //
+ // This field will be set automatically during packing.
+ Type Type
+
+ // Class is the class of network to which this DNS resource record
+ // pertains.
+ Class Class
+
+ // TTL is the length of time (measured in seconds) which this resource
+ // record is valid for (time to live). All Resources in a set should
+ // have the same TTL (RFC 2181 Section 5.2).
+ TTL uint32
+
+ // Length is the length of data in the resource record after the header.
+ //
+ // This field will be set automatically during packing.
+ Length uint16
+}
+
+// pack appends the wire format of the ResourceHeader to oldMsg.
+//
+// The bytes where length was packed are returned as a slice so they can be
+// updated after the rest of the Resource has been packed.
+func (h *ResourceHeader) pack(oldMsg []byte, compression map[string]int, compressionOff int) (msg []byte, length []byte, err error) {
+ msg = oldMsg
+ if msg, err = h.Name.pack(msg, compression, compressionOff); err != nil {
+ return oldMsg, nil, &nestedError{"Name", err}
+ }
+ msg = packType(msg, h.Type)
+ msg = packClass(msg, h.Class)
+ msg = packUint32(msg, h.TTL)
+ lenBegin := len(msg)
+ msg = packUint16(msg, h.Length)
+ return msg, msg[lenBegin : lenBegin+uint16Len], nil
+}
+
+func (h *ResourceHeader) unpack(msg []byte, off int) (int, error) {
+ newOff := off
+ var err error
+ if newOff, err = h.Name.unpack(msg, newOff); err != nil {
+ return off, &nestedError{"Name", err}
+ }
+ if h.Type, newOff, err = unpackType(msg, newOff); err != nil {
+ return off, &nestedError{"Type", err}
+ }
+ if h.Class, newOff, err = unpackClass(msg, newOff); err != nil {
+ return off, &nestedError{"Class", err}
+ }
+ if h.TTL, newOff, err = unpackUint32(msg, newOff); err != nil {
+ return off, &nestedError{"TTL", err}
+ }
+ if h.Length, newOff, err = unpackUint16(msg, newOff); err != nil {
+ return off, &nestedError{"Length", err}
+ }
+ return newOff, nil
+}
+
+func (h *ResourceHeader) fixLen(msg []byte, length []byte, preLen int) error {
+ conLen := len(msg) - preLen
+ if conLen > int(^uint16(0)) {
+ return errResTooLong
+ }
+
+ // Fill in the length now that we know how long the content is.
+ packUint16(length[:0], uint16(conLen))
+ h.Length = uint16(conLen)
+
+ return nil
+}
+
+// EDNS(0) wire costants.
+const (
+ edns0Version = 0
+
+ edns0DNSSECOK = 0x00008000
+ ednsVersionMask = 0x00ff0000
+ edns0DNSSECOKMask = 0x00ff8000
+)
+
+// SetEDNS0 configures h for EDNS(0).
+//
+// The provided extRCode must be an extedned RCode.
+func (h *ResourceHeader) SetEDNS0(udpPayloadLen int, extRCode RCode, dnssecOK bool) error {
+ h.Name = Name{Data: [nameLen]byte{'.'}, Length: 1} // RFC 6891 section 6.1.2
+ h.Type = TypeOPT
+ h.Class = Class(udpPayloadLen)
+ h.TTL = uint32(extRCode) >> 4 << 24
+ if dnssecOK {
+ h.TTL |= edns0DNSSECOK
+ }
+ return nil
+}
+
+// DNSSECAllowed reports whether the DNSSEC OK bit is set.
+func (h *ResourceHeader) DNSSECAllowed() bool {
+ return h.TTL&edns0DNSSECOKMask == edns0DNSSECOK // RFC 6891 section 6.1.3
+}
+
+// ExtendedRCode returns an extended RCode.
+//
+// The provided rcode must be the RCode in DNS message header.
+func (h *ResourceHeader) ExtendedRCode(rcode RCode) RCode {
+ if h.TTL&ednsVersionMask == edns0Version { // RFC 6891 section 6.1.3
+ return RCode(h.TTL>>24<<4) | rcode
+ }
+ return rcode
+}
+
+func skipResource(msg []byte, off int) (int, error) {
+ newOff, err := skipName(msg, off)
+ if err != nil {
+ return off, &nestedError{"Name", err}
+ }
+ if newOff, err = skipType(msg, newOff); err != nil {
+ return off, &nestedError{"Type", err}
+ }
+ if newOff, err = skipClass(msg, newOff); err != nil {
+ return off, &nestedError{"Class", err}
+ }
+ if newOff, err = skipUint32(msg, newOff); err != nil {
+ return off, &nestedError{"TTL", err}
+ }
+ length, newOff, err := unpackUint16(msg, newOff)
+ if err != nil {
+ return off, &nestedError{"Length", err}
+ }
+ if newOff += int(length); newOff > len(msg) {
+ return off, errResourceLen
+ }
+ return newOff, nil
+}
+
+// packUint16 appends the wire format of field to msg.
+func packUint16(msg []byte, field uint16) []byte {
+ return append(msg, byte(field>>8), byte(field))
+}
+
+func unpackUint16(msg []byte, off int) (uint16, int, error) {
+ if off+uint16Len > len(msg) {
+ return 0, off, errBaseLen
+ }
+ return uint16(msg[off])<<8 | uint16(msg[off+1]), off + uint16Len, nil
+}
+
+func skipUint16(msg []byte, off int) (int, error) {
+ if off+uint16Len > len(msg) {
+ return off, errBaseLen
+ }
+ return off + uint16Len, nil
+}
+
+// packType appends the wire format of field to msg.
+func packType(msg []byte, field Type) []byte {
+ return packUint16(msg, uint16(field))
+}
+
+func unpackType(msg []byte, off int) (Type, int, error) {
+ t, o, err := unpackUint16(msg, off)
+ return Type(t), o, err
+}
+
+func skipType(msg []byte, off int) (int, error) {
+ return skipUint16(msg, off)
+}
+
+// packClass appends the wire format of field to msg.
+func packClass(msg []byte, field Class) []byte {
+ return packUint16(msg, uint16(field))
+}
+
+func unpackClass(msg []byte, off int) (Class, int, error) {
+ c, o, err := unpackUint16(msg, off)
+ return Class(c), o, err
+}
+
+func skipClass(msg []byte, off int) (int, error) {
+ return skipUint16(msg, off)
+}
+
+// packUint32 appends the wire format of field to msg.
+func packUint32(msg []byte, field uint32) []byte {
+ return append(
+ msg,
+ byte(field>>24),
+ byte(field>>16),
+ byte(field>>8),
+ byte(field),
+ )
+}
+
+func unpackUint32(msg []byte, off int) (uint32, int, error) {
+ if off+uint32Len > len(msg) {
+ return 0, off, errBaseLen
+ }
+ v := uint32(msg[off])<<24 | uint32(msg[off+1])<<16 | uint32(msg[off+2])<<8 | uint32(msg[off+3])
+ return v, off + uint32Len, nil
+}
+
+func skipUint32(msg []byte, off int) (int, error) {
+ if off+uint32Len > len(msg) {
+ return off, errBaseLen
+ }
+ return off + uint32Len, nil
+}
+
+// packText appends the wire format of field to msg.
+func packText(msg []byte, field string) ([]byte, error) {
+ l := len(field)
+ if l > 255 {
+ return nil, errStringTooLong
+ }
+ msg = append(msg, byte(l))
+ msg = append(msg, field...)
+
+ return msg, nil
+}
+
+func unpackText(msg []byte, off int) (string, int, error) {
+ if off >= len(msg) {
+ return "", off, errBaseLen
+ }
+ beginOff := off + 1
+ endOff := beginOff + int(msg[off])
+ if endOff > len(msg) {
+ return "", off, errCalcLen
+ }
+ return string(msg[beginOff:endOff]), endOff, nil
+}
+
+func skipText(msg []byte, off int) (int, error) {
+ if off >= len(msg) {
+ return off, errBaseLen
+ }
+ endOff := off + 1 + int(msg[off])
+ if endOff > len(msg) {
+ return off, errCalcLen
+ }
+ return endOff, nil
+}
+
+// packBytes appends the wire format of field to msg.
+func packBytes(msg []byte, field []byte) []byte {
+ return append(msg, field...)
+}
+
+func unpackBytes(msg []byte, off int, field []byte) (int, error) {
+ newOff := off + len(field)
+ if newOff > len(msg) {
+ return off, errBaseLen
+ }
+ copy(field, msg[off:newOff])
+ return newOff, nil
+}
+
+func skipBytes(msg []byte, off int, field []byte) (int, error) {
+ newOff := off + len(field)
+ if newOff > len(msg) {
+ return off, errBaseLen
+ }
+ return newOff, nil
+}
+
+const nameLen = 255
+
+// A Name is a non-encoded domain name. It is used instead of strings to avoid
+// allocations.
+type Name struct {
+ Data [nameLen]byte
+ Length uint8
+}
+
+// NewName creates a new Name from a string.
+func NewName(name string) (Name, error) {
+ if len([]byte(name)) > nameLen {
+ return Name{}, errCalcLen
+ }
+ n := Name{Length: uint8(len(name))}
+ copy(n.Data[:], []byte(name))
+ return n, nil
+}
+
+func (n Name) String() string {
+ return string(n.Data[:n.Length])
+}
+
+// pack appends the wire format of the Name to msg.
+//
+// Domain names are a sequence of counted strings split at the dots. They end
+// with a zero-length string. Compression can be used to reuse domain suffixes.
+//
+// The compression map will be updated with new domain suffixes. If compression
+// is nil, compression will not be used.
+func (n *Name) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ oldMsg := msg
+
+ // Add a trailing dot to canonicalize name.
+ if n.Length == 0 || n.Data[n.Length-1] != '.' {
+ return oldMsg, errNonCanonicalName
+ }
+
+ // Allow root domain.
+ if n.Data[0] == '.' && n.Length == 1 {
+ return append(msg, 0), nil
+ }
+
+ // Emit sequence of counted strings, chopping at dots.
+ for i, begin := 0, 0; i < int(n.Length); i++ {
+ // Check for the end of the segment.
+ if n.Data[i] == '.' {
+ // The two most significant bits have special meaning.
+ // It isn't allowed for segments to be long enough to
+ // need them.
+ if i-begin >= 1<<6 {
+ return oldMsg, errSegTooLong
+ }
+
+ // Segments must have a non-zero length.
+ if i-begin == 0 {
+ return oldMsg, errZeroSegLen
+ }
+
+ msg = append(msg, byte(i-begin))
+
+ for j := begin; j < i; j++ {
+ msg = append(msg, n.Data[j])
+ }
+
+ begin = i + 1
+ continue
+ }
+
+ // We can only compress domain suffixes starting with a new
+ // segment. A pointer is two bytes with the two most significant
+ // bits set to 1 to indicate that it is a pointer.
+ if (i == 0 || n.Data[i-1] == '.') && compression != nil {
+ if ptr, ok := compression[string(n.Data[i:])]; ok {
+ // Hit. Emit a pointer instead of the rest of
+ // the domain.
+ return append(msg, byte(ptr>>8|0xC0), byte(ptr)), nil
+ }
+
+ // Miss. Add the suffix to the compression table if the
+ // offset can be stored in the available 14 bytes.
+ if len(msg) <= int(^uint16(0)>>2) {
+ compression[string(n.Data[i:])] = len(msg) - compressionOff
+ }
+ }
+ }
+ return append(msg, 0), nil
+}
+
+// unpack unpacks a domain name.
+func (n *Name) unpack(msg []byte, off int) (int, error) {
+ return n.unpackCompressed(msg, off, true /* allowCompression */)
+}
+
+func (n *Name) unpackCompressed(msg []byte, off int, allowCompression bool) (int, error) {
+ // currOff is the current working offset.
+ currOff := off
+
+ // newOff is the offset where the next record will start. Pointers lead
+ // to data that belongs to other names and thus doesn't count towards to
+ // the usage of this name.
+ newOff := off
+
+ // ptr is the number of pointers followed.
+ var ptr int
+
+ // Name is a slice representation of the name data.
+ name := n.Data[:0]
+
+Loop:
+ for {
+ if currOff >= len(msg) {
+ return off, errBaseLen
+ }
+ c := int(msg[currOff])
+ currOff++
+ switch c & 0xC0 {
+ case 0x00: // String segment
+ if c == 0x00 {
+ // A zero length signals the end of the name.
+ break Loop
+ }
+ endOff := currOff + c
+ if endOff > len(msg) {
+ return off, errCalcLen
+ }
+ name = append(name, msg[currOff:endOff]...)
+ name = append(name, '.')
+ currOff = endOff
+ case 0xC0: // Pointer
+ if !allowCompression {
+ return off, errCompressedSRV
+ }
+ if currOff >= len(msg) {
+ return off, errInvalidPtr
+ }
+ c1 := msg[currOff]
+ currOff++
+ if ptr == 0 {
+ newOff = currOff
+ }
+ // Don't follow too many pointers, maybe there's a loop.
+ if ptr++; ptr > 10 {
+ return off, errTooManyPtr
+ }
+ currOff = (c^0xC0)<<8 | int(c1)
+ default:
+ // Prefixes 0x80 and 0x40 are reserved.
+ return off, errReserved
+ }
+ }
+ if len(name) == 0 {
+ name = append(name, '.')
+ }
+ if len(name) > len(n.Data) {
+ return off, errCalcLen
+ }
+ n.Length = uint8(len(name))
+ if ptr == 0 {
+ newOff = currOff
+ }
+ return newOff, nil
+}
+
+func skipName(msg []byte, off int) (int, error) {
+ // newOff is the offset where the next record will start. Pointers lead
+ // to data that belongs to other names and thus doesn't count towards to
+ // the usage of this name.
+ newOff := off
+
+Loop:
+ for {
+ if newOff >= len(msg) {
+ return off, errBaseLen
+ }
+ c := int(msg[newOff])
+ newOff++
+ switch c & 0xC0 {
+ case 0x00:
+ if c == 0x00 {
+ // A zero length signals the end of the name.
+ break Loop
+ }
+ // literal string
+ newOff += c
+ if newOff > len(msg) {
+ return off, errCalcLen
+ }
+ case 0xC0:
+ // Pointer to somewhere else in msg.
+
+ // Pointers are two bytes.
+ newOff++
+
+ // Don't follow the pointer as the data here has ended.
+ break Loop
+ default:
+ // Prefixes 0x80 and 0x40 are reserved.
+ return off, errReserved
+ }
+ }
+
+ return newOff, nil
+}
+
+// A Question is a DNS query.
+type Question struct {
+ Name Name
+ Type Type
+ Class Class
+}
+
+// pack appends the wire format of the Question to msg.
+func (q *Question) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ msg, err := q.Name.pack(msg, compression, compressionOff)
+ if err != nil {
+ return msg, &nestedError{"Name", err}
+ }
+ msg = packType(msg, q.Type)
+ return packClass(msg, q.Class), nil
+}
+
+func unpackResourceBody(msg []byte, off int, hdr ResourceHeader) (ResourceBody, int, error) {
+ var (
+ r ResourceBody
+ err error
+ name string
+ )
+ switch hdr.Type {
+ case TypeA:
+ var rb AResource
+ rb, err = unpackAResource(msg, off)
+ r = &rb
+ name = "A"
+ case TypeNS:
+ var rb NSResource
+ rb, err = unpackNSResource(msg, off)
+ r = &rb
+ name = "NS"
+ case TypeCNAME:
+ var rb CNAMEResource
+ rb, err = unpackCNAMEResource(msg, off)
+ r = &rb
+ name = "CNAME"
+ case TypeSOA:
+ var rb SOAResource
+ rb, err = unpackSOAResource(msg, off)
+ r = &rb
+ name = "SOA"
+ case TypePTR:
+ var rb PTRResource
+ rb, err = unpackPTRResource(msg, off)
+ r = &rb
+ name = "PTR"
+ case TypeMX:
+ var rb MXResource
+ rb, err = unpackMXResource(msg, off)
+ r = &rb
+ name = "MX"
+ case TypeTXT:
+ var rb TXTResource
+ rb, err = unpackTXTResource(msg, off, hdr.Length)
+ r = &rb
+ name = "TXT"
+ case TypeAAAA:
+ var rb AAAAResource
+ rb, err = unpackAAAAResource(msg, off)
+ r = &rb
+ name = "AAAA"
+ case TypeSRV:
+ var rb SRVResource
+ rb, err = unpackSRVResource(msg, off)
+ r = &rb
+ name = "SRV"
+ case TypeOPT:
+ var rb OPTResource
+ rb, err = unpackOPTResource(msg, off, hdr.Length)
+ r = &rb
+ name = "OPT"
+ }
+ if err != nil {
+ return nil, off, &nestedError{name + " record", err}
+ }
+ if r == nil {
+ return nil, off, errors.New("invalid resource type: " + string(hdr.Type+'0'))
+ }
+ return r, off + int(hdr.Length), nil
+}
+
+// A CNAMEResource is a CNAME Resource record.
+type CNAMEResource struct {
+ CNAME Name
+}
+
+func (r *CNAMEResource) realType() Type {
+ return TypeCNAME
+}
+
+// pack appends the wire format of the CNAMEResource to msg.
+func (r *CNAMEResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ return r.CNAME.pack(msg, compression, compressionOff)
+}
+
+func unpackCNAMEResource(msg []byte, off int) (CNAMEResource, error) {
+ var cname Name
+ if _, err := cname.unpack(msg, off); err != nil {
+ return CNAMEResource{}, err
+ }
+ return CNAMEResource{cname}, nil
+}
+
+// An MXResource is an MX Resource record.
+type MXResource struct {
+ Pref uint16
+ MX Name
+}
+
+func (r *MXResource) realType() Type {
+ return TypeMX
+}
+
+// pack appends the wire format of the MXResource to msg.
+func (r *MXResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ oldMsg := msg
+ msg = packUint16(msg, r.Pref)
+ msg, err := r.MX.pack(msg, compression, compressionOff)
+ if err != nil {
+ return oldMsg, &nestedError{"MXResource.MX", err}
+ }
+ return msg, nil
+}
+
+func unpackMXResource(msg []byte, off int) (MXResource, error) {
+ pref, off, err := unpackUint16(msg, off)
+ if err != nil {
+ return MXResource{}, &nestedError{"Pref", err}
+ }
+ var mx Name
+ if _, err := mx.unpack(msg, off); err != nil {
+ return MXResource{}, &nestedError{"MX", err}
+ }
+ return MXResource{pref, mx}, nil
+}
+
+// An NSResource is an NS Resource record.
+type NSResource struct {
+ NS Name
+}
+
+func (r *NSResource) realType() Type {
+ return TypeNS
+}
+
+// pack appends the wire format of the NSResource to msg.
+func (r *NSResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ return r.NS.pack(msg, compression, compressionOff)
+}
+
+func unpackNSResource(msg []byte, off int) (NSResource, error) {
+ var ns Name
+ if _, err := ns.unpack(msg, off); err != nil {
+ return NSResource{}, err
+ }
+ return NSResource{ns}, nil
+}
+
+// A PTRResource is a PTR Resource record.
+type PTRResource struct {
+ PTR Name
+}
+
+func (r *PTRResource) realType() Type {
+ return TypePTR
+}
+
+// pack appends the wire format of the PTRResource to msg.
+func (r *PTRResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ return r.PTR.pack(msg, compression, compressionOff)
+}
+
+func unpackPTRResource(msg []byte, off int) (PTRResource, error) {
+ var ptr Name
+ if _, err := ptr.unpack(msg, off); err != nil {
+ return PTRResource{}, err
+ }
+ return PTRResource{ptr}, nil
+}
+
+// An SOAResource is an SOA Resource record.
+type SOAResource struct {
+ NS Name
+ MBox Name
+ Serial uint32
+ Refresh uint32
+ Retry uint32
+ Expire uint32
+
+ // MinTTL the is the default TTL of Resources records which did not
+ // contain a TTL value and the TTL of negative responses. (RFC 2308
+ // Section 4)
+ MinTTL uint32
+}
+
+func (r *SOAResource) realType() Type {
+ return TypeSOA
+}
+
+// pack appends the wire format of the SOAResource to msg.
+func (r *SOAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ oldMsg := msg
+ msg, err := r.NS.pack(msg, compression, compressionOff)
+ if err != nil {
+ return oldMsg, &nestedError{"SOAResource.NS", err}
+ }
+ msg, err = r.MBox.pack(msg, compression, compressionOff)
+ if err != nil {
+ return oldMsg, &nestedError{"SOAResource.MBox", err}
+ }
+ msg = packUint32(msg, r.Serial)
+ msg = packUint32(msg, r.Refresh)
+ msg = packUint32(msg, r.Retry)
+ msg = packUint32(msg, r.Expire)
+ return packUint32(msg, r.MinTTL), nil
+}
+
+func unpackSOAResource(msg []byte, off int) (SOAResource, error) {
+ var ns Name
+ off, err := ns.unpack(msg, off)
+ if err != nil {
+ return SOAResource{}, &nestedError{"NS", err}
+ }
+ var mbox Name
+ if off, err = mbox.unpack(msg, off); err != nil {
+ return SOAResource{}, &nestedError{"MBox", err}
+ }
+ serial, off, err := unpackUint32(msg, off)
+ if err != nil {
+ return SOAResource{}, &nestedError{"Serial", err}
+ }
+ refresh, off, err := unpackUint32(msg, off)
+ if err != nil {
+ return SOAResource{}, &nestedError{"Refresh", err}
+ }
+ retry, off, err := unpackUint32(msg, off)
+ if err != nil {
+ return SOAResource{}, &nestedError{"Retry", err}
+ }
+ expire, off, err := unpackUint32(msg, off)
+ if err != nil {
+ return SOAResource{}, &nestedError{"Expire", err}
+ }
+ minTTL, _, err := unpackUint32(msg, off)
+ if err != nil {
+ return SOAResource{}, &nestedError{"MinTTL", err}
+ }
+ return SOAResource{ns, mbox, serial, refresh, retry, expire, minTTL}, nil
+}
+
+// A TXTResource is a TXT Resource record.
+type TXTResource struct {
+ TXT []string
+}
+
+func (r *TXTResource) realType() Type {
+ return TypeTXT
+}
+
+// pack appends the wire format of the TXTResource to msg.
+func (r *TXTResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ oldMsg := msg
+ for _, s := range r.TXT {
+ var err error
+ msg, err = packText(msg, s)
+ if err != nil {
+ return oldMsg, err
+ }
+ }
+ return msg, nil
+}
+
+func unpackTXTResource(msg []byte, off int, length uint16) (TXTResource, error) {
+ txts := make([]string, 0, 1)
+ for n := uint16(0); n < length; {
+ var t string
+ var err error
+ if t, off, err = unpackText(msg, off); err != nil {
+ return TXTResource{}, &nestedError{"text", err}
+ }
+ // Check if we got too many bytes.
+ if length-n < uint16(len(t))+1 {
+ return TXTResource{}, errCalcLen
+ }
+ n += uint16(len(t)) + 1
+ txts = append(txts, t)
+ }
+ return TXTResource{txts}, nil
+}
+
+// An SRVResource is an SRV Resource record.
+type SRVResource struct {
+ Priority uint16
+ Weight uint16
+ Port uint16
+ Target Name // Not compressed as per RFC 2782.
+}
+
+func (r *SRVResource) realType() Type {
+ return TypeSRV
+}
+
+// pack appends the wire format of the SRVResource to msg.
+func (r *SRVResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ oldMsg := msg
+ msg = packUint16(msg, r.Priority)
+ msg = packUint16(msg, r.Weight)
+ msg = packUint16(msg, r.Port)
+ msg, err := r.Target.pack(msg, nil, compressionOff)
+ if err != nil {
+ return oldMsg, &nestedError{"SRVResource.Target", err}
+ }
+ return msg, nil
+}
+
+func unpackSRVResource(msg []byte, off int) (SRVResource, error) {
+ priority, off, err := unpackUint16(msg, off)
+ if err != nil {
+ return SRVResource{}, &nestedError{"Priority", err}
+ }
+ weight, off, err := unpackUint16(msg, off)
+ if err != nil {
+ return SRVResource{}, &nestedError{"Weight", err}
+ }
+ port, off, err := unpackUint16(msg, off)
+ if err != nil {
+ return SRVResource{}, &nestedError{"Port", err}
+ }
+ var target Name
+ if _, err := target.unpackCompressed(msg, off, false /* allowCompression */); err != nil {
+ return SRVResource{}, &nestedError{"Target", err}
+ }
+ return SRVResource{priority, weight, port, target}, nil
+}
+
+// An AResource is an A Resource record.
+type AResource struct {
+ A [4]byte
+}
+
+func (r *AResource) realType() Type {
+ return TypeA
+}
+
+// pack appends the wire format of the AResource to msg.
+func (r *AResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ return packBytes(msg, r.A[:]), nil
+}
+
+func unpackAResource(msg []byte, off int) (AResource, error) {
+ var a [4]byte
+ if _, err := unpackBytes(msg, off, a[:]); err != nil {
+ return AResource{}, err
+ }
+ return AResource{a}, nil
+}
+
+// An AAAAResource is an AAAA Resource record.
+type AAAAResource struct {
+ AAAA [16]byte
+}
+
+func (r *AAAAResource) realType() Type {
+ return TypeAAAA
+}
+
+// pack appends the wire format of the AAAAResource to msg.
+func (r *AAAAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ return packBytes(msg, r.AAAA[:]), nil
+}
+
+func unpackAAAAResource(msg []byte, off int) (AAAAResource, error) {
+ var aaaa [16]byte
+ if _, err := unpackBytes(msg, off, aaaa[:]); err != nil {
+ return AAAAResource{}, err
+ }
+ return AAAAResource{aaaa}, nil
+}
+
+// An OPTResource is an OPT pseudo Resource record.
+//
+// The pseudo resource record is part of the extension mechanisms for DNS
+// as defined in RFC 6891.
+type OPTResource struct {
+ Options []Option
+}
+
+// An Option represents a DNS message option within OPTResource.
+//
+// The message option is part of the extension mechanisms for DNS as
+// defined in RFC 6891.
+type Option struct {
+ Code uint16 // option code
+ Data []byte
+}
+
+func (r *OPTResource) realType() Type {
+ return TypeOPT
+}
+
+func (r *OPTResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) {
+ for _, opt := range r.Options {
+ msg = packUint16(msg, opt.Code)
+ l := uint16(len(opt.Data))
+ msg = packUint16(msg, l)
+ msg = packBytes(msg, opt.Data)
+ }
+ return msg, nil
+}
+
+func unpackOPTResource(msg []byte, off int, length uint16) (OPTResource, error) {
+ var opts []Option
+ for oldOff := off; off < oldOff+int(length); {
+ var err error
+ var o Option
+ o.Code, off, err = unpackUint16(msg, off)
+ if err != nil {
+ return OPTResource{}, &nestedError{"Code", err}
+ }
+ var l uint16
+ l, off, err = unpackUint16(msg, off)
+ if err != nil {
+ return OPTResource{}, &nestedError{"Data", err}
+ }
+ o.Data = make([]byte, l)
+ if copy(o.Data, msg[off:]) != int(l) {
+ return OPTResource{}, &nestedError{"Data", errCalcLen}
+ }
+ off += int(l)
+ opts = append(opts, o)
+ }
+ return OPTResource{opts}, nil
+}
diff --git a/vendor/golang.org/x/net/dns/dnsmessage/message_test.go b/vendor/golang.org/x/net/dns/dnsmessage/message_test.go
new file mode 100644
index 0000000..7e4e4bd
--- /dev/null
+++ b/vendor/golang.org/x/net/dns/dnsmessage/message_test.go
@@ -0,0 +1,1316 @@
+// Copyright 2009 The Go 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 dnsmessage
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func mustNewName(name string) Name {
+ n, err := NewName(name)
+ if err != nil {
+ panic(err)
+ }
+ return n
+}
+
+func mustEDNS0ResourceHeader(l int, extrc RCode, do bool) ResourceHeader {
+ h := ResourceHeader{Class: ClassINET}
+ if err := h.SetEDNS0(l, extrc, do); err != nil {
+ panic(err)
+ }
+ return h
+}
+
+func (m *Message) String() string {
+ s := fmt.Sprintf("Message: %#v\n", &m.Header)
+ if len(m.Questions) > 0 {
+ s += "-- Questions\n"
+ for _, q := range m.Questions {
+ s += fmt.Sprintf("%#v\n", q)
+ }
+ }
+ if len(m.Answers) > 0 {
+ s += "-- Answers\n"
+ for _, a := range m.Answers {
+ s += fmt.Sprintf("%#v\n", a)
+ }
+ }
+ if len(m.Authorities) > 0 {
+ s += "-- Authorities\n"
+ for _, ns := range m.Authorities {
+ s += fmt.Sprintf("%#v\n", ns)
+ }
+ }
+ if len(m.Additionals) > 0 {
+ s += "-- Additionals\n"
+ for _, e := range m.Additionals {
+ s += fmt.Sprintf("%#v\n", e)
+ }
+ }
+ return s
+}
+
+func TestNameString(t *testing.T) {
+ want := "foo"
+ name := mustNewName(want)
+ if got := fmt.Sprint(name); got != want {
+ t.Errorf("got fmt.Sprint(%#v) = %s, want = %s", name, got, want)
+ }
+}
+
+func TestQuestionPackUnpack(t *testing.T) {
+ want := Question{
+ Name: mustNewName("."),
+ Type: TypeA,
+ Class: ClassINET,
+ }
+ buf, err := want.pack(make([]byte, 1, 50), map[string]int{}, 1)
+ if err != nil {
+ t.Fatal("Question.pack() =", err)
+ }
+ var p Parser
+ p.msg = buf
+ p.header.questions = 1
+ p.section = sectionQuestions
+ p.off = 1
+ got, err := p.Question()
+ if err != nil {
+ t.Fatalf("Parser{%q}.Question() = %v", string(buf[1:]), err)
+ }
+ if p.off != len(buf) {
+ t.Errorf("unpacked different amount than packed: got = %d, want = %d", p.off, len(buf))
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("got from Parser.Question() = %+v, want = %+v", got, want)
+ }
+}
+
+func TestName(t *testing.T) {
+ tests := []string{
+ "",
+ ".",
+ "google..com",
+ "google.com",
+ "google..com.",
+ "google.com.",
+ ".google.com.",
+ "www..google.com.",
+ "www.google.com.",
+ }
+
+ for _, test := range tests {
+ n, err := NewName(test)
+ if err != nil {
+ t.Errorf("NewName(%q) = %v", test, err)
+ continue
+ }
+ if ns := n.String(); ns != test {
+ t.Errorf("got %#v.String() = %q, want = %q", n, ns, test)
+ continue
+ }
+ }
+}
+
+func TestNamePackUnpack(t *testing.T) {
+ tests := []struct {
+ in string
+ want string
+ err error
+ }{
+ {"", "", errNonCanonicalName},
+ {".", ".", nil},
+ {"google..com", "", errNonCanonicalName},
+ {"google.com", "", errNonCanonicalName},
+ {"google..com.", "", errZeroSegLen},
+ {"google.com.", "google.com.", nil},
+ {".google.com.", "", errZeroSegLen},
+ {"www..google.com.", "", errZeroSegLen},
+ {"www.google.com.", "www.google.com.", nil},
+ }
+
+ for _, test := range tests {
+ in := mustNewName(test.in)
+ want := mustNewName(test.want)
+ buf, err := in.pack(make([]byte, 0, 30), map[string]int{}, 0)
+ if err != test.err {
+ t.Errorf("got %q.pack() = %v, want = %v", test.in, err, test.err)
+ continue
+ }
+ if test.err != nil {
+ continue
+ }
+ var got Name
+ n, err := got.unpack(buf, 0)
+ if err != nil {
+ t.Errorf("%q.unpack() = %v", test.in, err)
+ continue
+ }
+ if n != len(buf) {
+ t.Errorf(
+ "unpacked different amount than packed for %q: got = %d, want = %d",
+ test.in,
+ n,
+ len(buf),
+ )
+ }
+ if got != want {
+ t.Errorf("unpacking packing of %q: got = %#v, want = %#v", test.in, got, want)
+ }
+ }
+}
+
+func TestIncompressibleName(t *testing.T) {
+ name := mustNewName("example.com.")
+ compression := map[string]int{}
+ buf, err := name.pack(make([]byte, 0, 100), compression, 0)
+ if err != nil {
+ t.Fatal("first Name.pack() =", err)
+ }
+ buf, err = name.pack(buf, compression, 0)
+ if err != nil {
+ t.Fatal("second Name.pack() =", err)
+ }
+ var n1 Name
+ off, err := n1.unpackCompressed(buf, 0, false /* allowCompression */)
+ if err != nil {
+ t.Fatal("unpacking incompressible name without pointers failed:", err)
+ }
+ var n2 Name
+ if _, err := n2.unpackCompressed(buf, off, false /* allowCompression */); err != errCompressedSRV {
+ t.Errorf("unpacking compressed incompressible name with pointers: got %v, want = %v", err, errCompressedSRV)
+ }
+}
+
+func checkErrorPrefix(err error, prefix string) bool {
+ e, ok := err.(*nestedError)
+ return ok && e.s == prefix
+}
+
+func TestHeaderUnpackError(t *testing.T) {
+ wants := []string{
+ "id",
+ "bits",
+ "questions",
+ "answers",
+ "authorities",
+ "additionals",
+ }
+ var buf []byte
+ var h header
+ for _, want := range wants {
+ n, err := h.unpack(buf, 0)
+ if n != 0 || !checkErrorPrefix(err, want) {
+ t.Errorf("got header.unpack([%d]byte, 0) = %d, %v, want = 0, %s", len(buf), n, err, want)
+ }
+ buf = append(buf, 0, 0)
+ }
+}
+
+func TestParserStart(t *testing.T) {
+ const want = "unpacking header"
+ var p Parser
+ for i := 0; i <= 1; i++ {
+ _, err := p.Start([]byte{})
+ if !checkErrorPrefix(err, want) {
+ t.Errorf("got Parser.Start(nil) = _, %v, want = _, %s", err, want)
+ }
+ }
+}
+
+func TestResourceNotStarted(t *testing.T) {
+ tests := []struct {
+ name string
+ fn func(*Parser) error
+ }{
+ {"CNAMEResource", func(p *Parser) error { _, err := p.CNAMEResource(); return err }},
+ {"MXResource", func(p *Parser) error { _, err := p.MXResource(); return err }},
+ {"NSResource", func(p *Parser) error { _, err := p.NSResource(); return err }},
+ {"PTRResource", func(p *Parser) error { _, err := p.PTRResource(); return err }},
+ {"SOAResource", func(p *Parser) error { _, err := p.SOAResource(); return err }},
+ {"TXTResource", func(p *Parser) error { _, err := p.TXTResource(); return err }},
+ {"SRVResource", func(p *Parser) error { _, err := p.SRVResource(); return err }},
+ {"AResource", func(p *Parser) error { _, err := p.AResource(); return err }},
+ {"AAAAResource", func(p *Parser) error { _, err := p.AAAAResource(); return err }},
+ }
+
+ for _, test := range tests {
+ if err := test.fn(&Parser{}); err != ErrNotStarted {
+ t.Errorf("got Parser.%s() = _ , %v, want = _, %v", test.name, err, ErrNotStarted)
+ }
+ }
+}
+
+func TestDNSPackUnpack(t *testing.T) {
+ wants := []Message{
+ {
+ Questions: []Question{
+ {
+ Name: mustNewName("."),
+ Type: TypeAAAA,
+ Class: ClassINET,
+ },
+ },
+ Answers: []Resource{},
+ Authorities: []Resource{},
+ Additionals: []Resource{},
+ },
+ largeTestMsg(),
+ }
+ for i, want := range wants {
+ b, err := want.Pack()
+ if err != nil {
+ t.Fatalf("%d: Message.Pack() = %v", i, err)
+ }
+ var got Message
+ err = got.Unpack(b)
+ if err != nil {
+ t.Fatalf("%d: Message.Unapck() = %v", i, err)
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("%d: Message.Pack/Unpack() roundtrip: got = %+v, want = %+v", i, &got, &want)
+ }
+ }
+}
+
+func TestDNSAppendPackUnpack(t *testing.T) {
+ wants := []Message{
+ {
+ Questions: []Question{
+ {
+ Name: mustNewName("."),
+ Type: TypeAAAA,
+ Class: ClassINET,
+ },
+ },
+ Answers: []Resource{},
+ Authorities: []Resource{},
+ Additionals: []Resource{},
+ },
+ largeTestMsg(),
+ }
+ for i, want := range wants {
+ b := make([]byte, 2, 514)
+ b, err := want.AppendPack(b)
+ if err != nil {
+ t.Fatalf("%d: Message.AppendPack() = %v", i, err)
+ }
+ b = b[2:]
+ var got Message
+ err = got.Unpack(b)
+ if err != nil {
+ t.Fatalf("%d: Message.Unapck() = %v", i, err)
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("%d: Message.AppendPack/Unpack() roundtrip: got = %+v, want = %+v", i, &got, &want)
+ }
+ }
+}
+
+func TestSkipAll(t *testing.T) {
+ msg := largeTestMsg()
+ buf, err := msg.Pack()
+ if err != nil {
+ t.Fatal("Message.Pack() =", err)
+ }
+ var p Parser
+ if _, err := p.Start(buf); err != nil {
+ t.Fatal("Parser.Start(non-nil) =", err)
+ }
+
+ tests := []struct {
+ name string
+ f func() error
+ }{
+ {"SkipAllQuestions", p.SkipAllQuestions},
+ {"SkipAllAnswers", p.SkipAllAnswers},
+ {"SkipAllAuthorities", p.SkipAllAuthorities},
+ {"SkipAllAdditionals", p.SkipAllAdditionals},
+ }
+ for _, test := range tests {
+ for i := 1; i <= 3; i++ {
+ if err := test.f(); err != nil {
+ t.Errorf("%d: Parser.%s() = %v", i, test.name, err)
+ }
+ }
+ }
+}
+
+func TestSkipEach(t *testing.T) {
+ msg := smallTestMsg()
+
+ buf, err := msg.Pack()
+ if err != nil {
+ t.Fatal("Message.Pack() =", err)
+ }
+ var p Parser
+ if _, err := p.Start(buf); err != nil {
+ t.Fatal("Parser.Start(non-nil) =", err)
+ }
+
+ tests := []struct {
+ name string
+ f func() error
+ }{
+ {"SkipQuestion", p.SkipQuestion},
+ {"SkipAnswer", p.SkipAnswer},
+ {"SkipAuthority", p.SkipAuthority},
+ {"SkipAdditional", p.SkipAdditional},
+ }
+ for _, test := range tests {
+ if err := test.f(); err != nil {
+ t.Errorf("first Parser.%s() = %v, want = nil", test.name, err)
+ }
+ if err := test.f(); err != ErrSectionDone {
+ t.Errorf("second Parser.%s() = %v, want = %v", test.name, err, ErrSectionDone)
+ }
+ }
+}
+
+func TestSkipAfterRead(t *testing.T) {
+ msg := smallTestMsg()
+
+ buf, err := msg.Pack()
+ if err != nil {
+ t.Fatal("Message.Pack() =", err)
+ }
+ var p Parser
+ if _, err := p.Start(buf); err != nil {
+ t.Fatal("Parser.Srart(non-nil) =", err)
+ }
+
+ tests := []struct {
+ name string
+ skip func() error
+ read func() error
+ }{
+ {"Question", p.SkipQuestion, func() error { _, err := p.Question(); return err }},
+ {"Answer", p.SkipAnswer, func() error { _, err := p.Answer(); return err }},
+ {"Authority", p.SkipAuthority, func() error { _, err := p.Authority(); return err }},
+ {"Additional", p.SkipAdditional, func() error { _, err := p.Additional(); return err }},
+ }
+ for _, test := range tests {
+ if err := test.read(); err != nil {
+ t.Errorf("got Parser.%s() = _, %v, want = _, nil", test.name, err)
+ }
+ if err := test.skip(); err != ErrSectionDone {
+ t.Errorf("got Parser.Skip%s() = %v, want = %v", test.name, err, ErrSectionDone)
+ }
+ }
+}
+
+func TestSkipNotStarted(t *testing.T) {
+ var p Parser
+
+ tests := []struct {
+ name string
+ f func() error
+ }{
+ {"SkipAllQuestions", p.SkipAllQuestions},
+ {"SkipAllAnswers", p.SkipAllAnswers},
+ {"SkipAllAuthorities", p.SkipAllAuthorities},
+ {"SkipAllAdditionals", p.SkipAllAdditionals},
+ }
+ for _, test := range tests {
+ if err := test.f(); err != ErrNotStarted {
+ t.Errorf("got Parser.%s() = %v, want = %v", test.name, err, ErrNotStarted)
+ }
+ }
+}
+
+func TestTooManyRecords(t *testing.T) {
+ const recs = int(^uint16(0)) + 1
+ tests := []struct {
+ name string
+ msg Message
+ want error
+ }{
+ {
+ "Questions",
+ Message{
+ Questions: make([]Question, recs),
+ },
+ errTooManyQuestions,
+ },
+ {
+ "Answers",
+ Message{
+ Answers: make([]Resource, recs),
+ },
+ errTooManyAnswers,
+ },
+ {
+ "Authorities",
+ Message{
+ Authorities: make([]Resource, recs),
+ },
+ errTooManyAuthorities,
+ },
+ {
+ "Additionals",
+ Message{
+ Additionals: make([]Resource, recs),
+ },
+ errTooManyAdditionals,
+ },
+ }
+
+ for _, test := range tests {
+ if _, got := test.msg.Pack(); got != test.want {
+ t.Errorf("got Message.Pack() for %d %s = %v, want = %v", recs, test.name, got, test.want)
+ }
+ }
+}
+
+func TestVeryLongTxt(t *testing.T) {
+ want := Resource{
+ ResourceHeader{
+ Name: mustNewName("foo.bar.example.com."),
+ Type: TypeTXT,
+ Class: ClassINET,
+ },
+ &TXTResource{[]string{
+ "",
+ "",
+ "foo bar",
+ "",
+ "www.example.com",
+ "www.example.com.",
+ strings.Repeat(".", 255),
+ }},
+ }
+ buf, err := want.pack(make([]byte, 0, 8000), map[string]int{}, 0)
+ if err != nil {
+ t.Fatal("Resource.pack() =", err)
+ }
+ var got Resource
+ off, err := got.Header.unpack(buf, 0)
+ if err != nil {
+ t.Fatal("ResourceHeader.unpack() =", err)
+ }
+ body, n, err := unpackResourceBody(buf, off, got.Header)
+ if err != nil {
+ t.Fatal("unpackResourceBody() =", err)
+ }
+ got.Body = body
+ if n != len(buf) {
+ t.Errorf("unpacked different amount than packed: got = %d, want = %d", n, len(buf))
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("Resource.pack/unpack() roundtrip: got = %#v, want = %#v", got, want)
+ }
+}
+
+func TestTooLongTxt(t *testing.T) {
+ rb := TXTResource{[]string{strings.Repeat(".", 256)}}
+ if _, err := rb.pack(make([]byte, 0, 8000), map[string]int{}, 0); err != errStringTooLong {
+ t.Errorf("packing TXTResource with 256 character string: got err = %v, want = %v", err, errStringTooLong)
+ }
+}
+
+func TestStartAppends(t *testing.T) {
+ buf := make([]byte, 2, 514)
+ wantBuf := []byte{4, 44}
+ copy(buf, wantBuf)
+
+ b := NewBuilder(buf, Header{})
+ b.EnableCompression()
+
+ buf, err := b.Finish()
+ if err != nil {
+ t.Fatal("Builder.Finish() =", err)
+ }
+ if got, want := len(buf), headerLen+2; got != want {
+ t.Errorf("got len(buf) = %d, want = %d", got, want)
+ }
+ if string(buf[:2]) != string(wantBuf) {
+ t.Errorf("original data not preserved, got = %#v, want = %#v", buf[:2], wantBuf)
+ }
+}
+
+func TestStartError(t *testing.T) {
+ tests := []struct {
+ name string
+ fn func(*Builder) error
+ }{
+ {"Questions", func(b *Builder) error { return b.StartQuestions() }},
+ {"Answers", func(b *Builder) error { return b.StartAnswers() }},
+ {"Authorities", func(b *Builder) error { return b.StartAuthorities() }},
+ {"Additionals", func(b *Builder) error { return b.StartAdditionals() }},
+ }
+
+ envs := []struct {
+ name string
+ fn func() *Builder
+ want error
+ }{
+ {"sectionNotStarted", func() *Builder { return &Builder{section: sectionNotStarted} }, ErrNotStarted},
+ {"sectionDone", func() *Builder { return &Builder{section: sectionDone} }, ErrSectionDone},
+ }
+
+ for _, env := range envs {
+ for _, test := range tests {
+ if got := test.fn(env.fn()); got != env.want {
+ t.Errorf("got Builder{%s}.Start%s() = %v, want = %v", env.name, test.name, got, env.want)
+ }
+ }
+ }
+}
+
+func TestBuilderResourceError(t *testing.T) {
+ tests := []struct {
+ name string
+ fn func(*Builder) error
+ }{
+ {"CNAMEResource", func(b *Builder) error { return b.CNAMEResource(ResourceHeader{}, CNAMEResource{}) }},
+ {"MXResource", func(b *Builder) error { return b.MXResource(ResourceHeader{}, MXResource{}) }},
+ {"NSResource", func(b *Builder) error { return b.NSResource(ResourceHeader{}, NSResource{}) }},
+ {"PTRResource", func(b *Builder) error { return b.PTRResource(ResourceHeader{}, PTRResource{}) }},
+ {"SOAResource", func(b *Builder) error { return b.SOAResource(ResourceHeader{}, SOAResource{}) }},
+ {"TXTResource", func(b *Builder) error { return b.TXTResource(ResourceHeader{}, TXTResource{}) }},
+ {"SRVResource", func(b *Builder) error { return b.SRVResource(ResourceHeader{}, SRVResource{}) }},
+ {"AResource", func(b *Builder) error { return b.AResource(ResourceHeader{}, AResource{}) }},
+ {"AAAAResource", func(b *Builder) error { return b.AAAAResource(ResourceHeader{}, AAAAResource{}) }},
+ {"OPTResource", func(b *Builder) error { return b.OPTResource(ResourceHeader{}, OPTResource{}) }},
+ }
+
+ envs := []struct {
+ name string
+ fn func() *Builder
+ want error
+ }{
+ {"sectionNotStarted", func() *Builder { return &Builder{section: sectionNotStarted} }, ErrNotStarted},
+ {"sectionHeader", func() *Builder { return &Builder{section: sectionHeader} }, ErrNotStarted},
+ {"sectionQuestions", func() *Builder { return &Builder{section: sectionQuestions} }, ErrNotStarted},
+ {"sectionDone", func() *Builder { return &Builder{section: sectionDone} }, ErrSectionDone},
+ }
+
+ for _, env := range envs {
+ for _, test := range tests {
+ if got := test.fn(env.fn()); got != env.want {
+ t.Errorf("got Builder{%s}.%s() = %v, want = %v", env.name, test.name, got, env.want)
+ }
+ }
+ }
+}
+
+func TestFinishError(t *testing.T) {
+ var b Builder
+ want := ErrNotStarted
+ if _, got := b.Finish(); got != want {
+ t.Errorf("got Builder.Finish() = %v, want = %v", got, want)
+ }
+}
+
+func TestBuilder(t *testing.T) {
+ msg := largeTestMsg()
+ want, err := msg.Pack()
+ if err != nil {
+ t.Fatal("Message.Pack() =", err)
+ }
+
+ b := NewBuilder(nil, msg.Header)
+ b.EnableCompression()
+
+ if err := b.StartQuestions(); err != nil {
+ t.Fatal("Builder.StartQuestions() =", err)
+ }
+ for _, q := range msg.Questions {
+ if err := b.Question(q); err != nil {
+ t.Fatalf("Builder.Question(%#v) = %v", q, err)
+ }
+ }
+
+ if err := b.StartAnswers(); err != nil {
+ t.Fatal("Builder.StartAnswers() =", err)
+ }
+ for _, a := range msg.Answers {
+ switch a.Header.Type {
+ case TypeA:
+ if err := b.AResource(a.Header, *a.Body.(*AResource)); err != nil {
+ t.Fatalf("Builder.AResource(%#v) = %v", a, err)
+ }
+ case TypeNS:
+ if err := b.NSResource(a.Header, *a.Body.(*NSResource)); err != nil {
+ t.Fatalf("Builder.NSResource(%#v) = %v", a, err)
+ }
+ case TypeCNAME:
+ if err := b.CNAMEResource(a.Header, *a.Body.(*CNAMEResource)); err != nil {
+ t.Fatalf("Builder.CNAMEResource(%#v) = %v", a, err)
+ }
+ case TypeSOA:
+ if err := b.SOAResource(a.Header, *a.Body.(*SOAResource)); err != nil {
+ t.Fatalf("Builder.SOAResource(%#v) = %v", a, err)
+ }
+ case TypePTR:
+ if err := b.PTRResource(a.Header, *a.Body.(*PTRResource)); err != nil {
+ t.Fatalf("Builder.PTRResource(%#v) = %v", a, err)
+ }
+ case TypeMX:
+ if err := b.MXResource(a.Header, *a.Body.(*MXResource)); err != nil {
+ t.Fatalf("Builder.MXResource(%#v) = %v", a, err)
+ }
+ case TypeTXT:
+ if err := b.TXTResource(a.Header, *a.Body.(*TXTResource)); err != nil {
+ t.Fatalf("Builder.TXTResource(%#v) = %v", a, err)
+ }
+ case TypeAAAA:
+ if err := b.AAAAResource(a.Header, *a.Body.(*AAAAResource)); err != nil {
+ t.Fatalf("Builder.AAAAResource(%#v) = %v", a, err)
+ }
+ case TypeSRV:
+ if err := b.SRVResource(a.Header, *a.Body.(*SRVResource)); err != nil {
+ t.Fatalf("Builder.SRVResource(%#v) = %v", a, err)
+ }
+ }
+ }
+
+ if err := b.StartAuthorities(); err != nil {
+ t.Fatal("Builder.StartAuthorities() =", err)
+ }
+ for _, a := range msg.Authorities {
+ if err := b.NSResource(a.Header, *a.Body.(*NSResource)); err != nil {
+ t.Fatalf("Builder.NSResource(%#v) = %v", a, err)
+ }
+ }
+
+ if err := b.StartAdditionals(); err != nil {
+ t.Fatal("Builder.StartAdditionals() =", err)
+ }
+ for _, a := range msg.Additionals {
+ switch a.Body.(type) {
+ case *TXTResource:
+ if err := b.TXTResource(a.Header, *a.Body.(*TXTResource)); err != nil {
+ t.Fatalf("Builder.TXTResource(%#v) = %v", a, err)
+ }
+ case *OPTResource:
+ if err := b.OPTResource(a.Header, *a.Body.(*OPTResource)); err != nil {
+ t.Fatalf("Builder.OPTResource(%#v) = %v", a, err)
+ }
+ }
+ }
+
+ got, err := b.Finish()
+ if err != nil {
+ t.Fatal("Builder.Finish() =", err)
+ }
+ if !bytes.Equal(got, want) {
+ t.Fatalf("got from Builder.Finish() = %#v\nwant = %#v", got, want)
+ }
+}
+
+func TestResourcePack(t *testing.T) {
+ for _, tt := range []struct {
+ m Message
+ err error
+ }{
+ {
+ Message{
+ Questions: []Question{
+ {
+ Name: mustNewName("."),
+ Type: TypeAAAA,
+ Class: ClassINET,
+ },
+ },
+ Answers: []Resource{{ResourceHeader{}, nil}},
+ },
+ &nestedError{"packing Answer", errNilResouceBody},
+ },
+ {
+ Message{
+ Questions: []Question{
+ {
+ Name: mustNewName("."),
+ Type: TypeAAAA,
+ Class: ClassINET,
+ },
+ },
+ Authorities: []Resource{{ResourceHeader{}, (*NSResource)(nil)}},
+ },
+ &nestedError{"packing Authority",
+ &nestedError{"ResourceHeader",
+ &nestedError{"Name", errNonCanonicalName},
+ },
+ },
+ },
+ {
+ Message{
+ Questions: []Question{
+ {
+ Name: mustNewName("."),
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ },
+ Additionals: []Resource{{ResourceHeader{}, nil}},
+ },
+ &nestedError{"packing Additional", errNilResouceBody},
+ },
+ } {
+ _, err := tt.m.Pack()
+ if !reflect.DeepEqual(err, tt.err) {
+ t.Errorf("got Message{%v}.Pack() = %v, want %v", tt.m, err, tt.err)
+ }
+ }
+}
+
+func TestOptionPackUnpack(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ w []byte // wire format of m.Additionals
+ m Message
+ dnssecOK bool
+ extRCode RCode
+ }{
+ {
+ name: "without EDNS(0) options",
+ w: []byte{
+ 0x00, 0x00, 0x29, 0x10, 0x00, 0xfe, 0x00, 0x80,
+ 0x00, 0x00, 0x00,
+ },
+ m: Message{
+ Header: Header{RCode: RCodeFormatError},
+ Questions: []Question{
+ {
+ Name: mustNewName("."),
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ },
+ Additionals: []Resource{
+ {
+ mustEDNS0ResourceHeader(4096, 0xfe0|RCodeFormatError, true),
+ &OPTResource{},
+ },
+ },
+ },
+ dnssecOK: true,
+ extRCode: 0xfe0 | RCodeFormatError,
+ },
+ {
+ name: "with EDNS(0) options",
+ w: []byte{
+ 0x00, 0x00, 0x29, 0x10, 0x00, 0xff, 0x00, 0x00,
+ 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x02, 0x00,
+ 0x00, 0x00, 0x0b, 0x00, 0x02, 0x12, 0x34,
+ },
+ m: Message{
+ Header: Header{RCode: RCodeServerFailure},
+ Questions: []Question{
+ {
+ Name: mustNewName("."),
+ Type: TypeAAAA,
+ Class: ClassINET,
+ },
+ },
+ Additionals: []Resource{
+ {
+ mustEDNS0ResourceHeader(4096, 0xff0|RCodeServerFailure, false),
+ &OPTResource{
+ Options: []Option{
+ {
+ Code: 12, // see RFC 7828
+ Data: []byte{0x00, 0x00},
+ },
+ {
+ Code: 11, // see RFC 7830
+ Data: []byte{0x12, 0x34},
+ },
+ },
+ },
+ },
+ },
+ },
+ dnssecOK: false,
+ extRCode: 0xff0 | RCodeServerFailure,
+ },
+ {
+ // Containing multiple OPT resources in a
+ // message is invalid, but it's necessary for
+ // protocol conformance testing.
+ name: "with multiple OPT resources",
+ w: []byte{
+ 0x00, 0x00, 0x29, 0x10, 0x00, 0xff, 0x00, 0x00,
+ 0x00, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x02, 0x12,
+ 0x34, 0x00, 0x00, 0x29, 0x10, 0x00, 0xff, 0x00,
+ 0x00, 0x00, 0x00, 0x06, 0x00, 0x0c, 0x00, 0x02,
+ 0x00, 0x00,
+ },
+ m: Message{
+ Header: Header{RCode: RCodeNameError},
+ Questions: []Question{
+ {
+ Name: mustNewName("."),
+ Type: TypeAAAA,
+ Class: ClassINET,
+ },
+ },
+ Additionals: []Resource{
+ {
+ mustEDNS0ResourceHeader(4096, 0xff0|RCodeNameError, false),
+ &OPTResource{
+ Options: []Option{
+ {
+ Code: 11, // see RFC 7830
+ Data: []byte{0x12, 0x34},
+ },
+ },
+ },
+ },
+ {
+ mustEDNS0ResourceHeader(4096, 0xff0|RCodeNameError, false),
+ &OPTResource{
+ Options: []Option{
+ {
+ Code: 12, // see RFC 7828
+ Data: []byte{0x00, 0x00},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ } {
+ w, err := tt.m.Pack()
+ if err != nil {
+ t.Errorf("Message.Pack() for %s = %v", tt.name, err)
+ continue
+ }
+ if !bytes.Equal(w[len(w)-len(tt.w):], tt.w) {
+ t.Errorf("got Message.Pack() for %s = %#v, want %#v", tt.name, w[len(w)-len(tt.w):], tt.w)
+ continue
+ }
+ var m Message
+ if err := m.Unpack(w); err != nil {
+ t.Errorf("Message.Unpack() for %s = %v", tt.name, err)
+ continue
+ }
+ if !reflect.DeepEqual(m.Additionals, tt.m.Additionals) {
+ t.Errorf("got Message.Pack/Unpack() roundtrip for %s = %+v, want %+v", tt.name, m, tt.m)
+ continue
+ }
+ }
+}
+
+func benchmarkParsingSetup() ([]byte, error) {
+ name := mustNewName("foo.bar.example.com.")
+ msg := Message{
+ Header: Header{Response: true, Authoritative: true},
+ Questions: []Question{
+ {
+ Name: name,
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ },
+ Answers: []Resource{
+ {
+ ResourceHeader{
+ Name: name,
+ Class: ClassINET,
+ },
+ &AResource{[4]byte{}},
+ },
+ {
+ ResourceHeader{
+ Name: name,
+ Class: ClassINET,
+ },
+ &AAAAResource{[16]byte{}},
+ },
+ {
+ ResourceHeader{
+ Name: name,
+ Class: ClassINET,
+ },
+ &CNAMEResource{name},
+ },
+ {
+ ResourceHeader{
+ Name: name,
+ Class: ClassINET,
+ },
+ &NSResource{name},
+ },
+ },
+ }
+
+ buf, err := msg.Pack()
+ if err != nil {
+ return nil, fmt.Errorf("Message.Pack() = %v", err)
+ }
+ return buf, nil
+}
+
+func benchmarkParsing(tb testing.TB, buf []byte) {
+ var p Parser
+ if _, err := p.Start(buf); err != nil {
+ tb.Fatal("Parser.Start(non-nil) =", err)
+ }
+
+ for {
+ _, err := p.Question()
+ if err == ErrSectionDone {
+ break
+ }
+ if err != nil {
+ tb.Fatal("Parser.Question() =", err)
+ }
+ }
+
+ for {
+ h, err := p.AnswerHeader()
+ if err == ErrSectionDone {
+ break
+ }
+ if err != nil {
+ tb.Fatal("Parser.AnswerHeader() =", err)
+ }
+
+ switch h.Type {
+ case TypeA:
+ if _, err := p.AResource(); err != nil {
+ tb.Fatal("Parser.AResource() =", err)
+ }
+ case TypeAAAA:
+ if _, err := p.AAAAResource(); err != nil {
+ tb.Fatal("Parser.AAAAResource() =", err)
+ }
+ case TypeCNAME:
+ if _, err := p.CNAMEResource(); err != nil {
+ tb.Fatal("Parser.CNAMEResource() =", err)
+ }
+ case TypeNS:
+ if _, err := p.NSResource(); err != nil {
+ tb.Fatal("Parser.NSResource() =", err)
+ }
+ case TypeOPT:
+ if _, err := p.OPTResource(); err != nil {
+ tb.Fatal("Parser.OPTResource() =", err)
+ }
+ default:
+ tb.Fatalf("got unknown type: %T", h)
+ }
+ }
+}
+
+func BenchmarkParsing(b *testing.B) {
+ buf, err := benchmarkParsingSetup()
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ benchmarkParsing(b, buf)
+ }
+}
+
+func TestParsingAllocs(t *testing.T) {
+ buf, err := benchmarkParsingSetup()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if allocs := testing.AllocsPerRun(100, func() { benchmarkParsing(t, buf) }); allocs > 0.5 {
+ t.Errorf("allocations during parsing: got = %f, want ~0", allocs)
+ }
+}
+
+func benchmarkBuildingSetup() (Name, []byte) {
+ name := mustNewName("foo.bar.example.com.")
+ buf := make([]byte, 0, packStartingCap)
+ return name, buf
+}
+
+func benchmarkBuilding(tb testing.TB, name Name, buf []byte) {
+ bld := NewBuilder(buf, Header{Response: true, Authoritative: true})
+
+ if err := bld.StartQuestions(); err != nil {
+ tb.Fatal("Builder.StartQuestions() =", err)
+ }
+ q := Question{
+ Name: name,
+ Type: TypeA,
+ Class: ClassINET,
+ }
+ if err := bld.Question(q); err != nil {
+ tb.Fatalf("Builder.Question(%+v) = %v", q, err)
+ }
+
+ hdr := ResourceHeader{
+ Name: name,
+ Class: ClassINET,
+ }
+ if err := bld.StartAnswers(); err != nil {
+ tb.Fatal("Builder.StartQuestions() =", err)
+ }
+
+ ar := AResource{[4]byte{}}
+ if err := bld.AResource(hdr, ar); err != nil {
+ tb.Fatalf("Builder.AResource(%+v, %+v) = %v", hdr, ar, err)
+ }
+
+ aaar := AAAAResource{[16]byte{}}
+ if err := bld.AAAAResource(hdr, aaar); err != nil {
+ tb.Fatalf("Builder.AAAAResource(%+v, %+v) = %v", hdr, aaar, err)
+ }
+
+ cnr := CNAMEResource{name}
+ if err := bld.CNAMEResource(hdr, cnr); err != nil {
+ tb.Fatalf("Builder.CNAMEResource(%+v, %+v) = %v", hdr, cnr, err)
+ }
+
+ nsr := NSResource{name}
+ if err := bld.NSResource(hdr, nsr); err != nil {
+ tb.Fatalf("Builder.NSResource(%+v, %+v) = %v", hdr, nsr, err)
+ }
+
+ extrc := 0xfe0 | RCodeNotImplemented
+ if err := (&hdr).SetEDNS0(4096, extrc, true); err != nil {
+ tb.Fatalf("ResourceHeader.SetEDNS0(4096, %#x, true) = %v", extrc, err)
+ }
+ optr := OPTResource{}
+ if err := bld.OPTResource(hdr, optr); err != nil {
+ tb.Fatalf("Builder.OPTResource(%+v, %+v) = %v", hdr, optr, err)
+ }
+
+ if _, err := bld.Finish(); err != nil {
+ tb.Fatal("Builder.Finish() =", err)
+ }
+}
+
+func BenchmarkBuilding(b *testing.B) {
+ name, buf := benchmarkBuildingSetup()
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ benchmarkBuilding(b, name, buf)
+ }
+}
+
+func TestBuildingAllocs(t *testing.T) {
+ name, buf := benchmarkBuildingSetup()
+ if allocs := testing.AllocsPerRun(100, func() { benchmarkBuilding(t, name, buf) }); allocs > 0.5 {
+ t.Errorf("allocations during building: got = %f, want ~0", allocs)
+ }
+}
+
+func smallTestMsg() Message {
+ name := mustNewName("example.com.")
+ return Message{
+ Header: Header{Response: true, Authoritative: true},
+ Questions: []Question{
+ {
+ Name: name,
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ },
+ Answers: []Resource{
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ &AResource{[4]byte{127, 0, 0, 1}},
+ },
+ },
+ Authorities: []Resource{
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ &AResource{[4]byte{127, 0, 0, 1}},
+ },
+ },
+ Additionals: []Resource{
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ &AResource{[4]byte{127, 0, 0, 1}},
+ },
+ },
+ }
+}
+
+func BenchmarkPack(b *testing.B) {
+ msg := largeTestMsg()
+
+ b.ReportAllocs()
+
+ for i := 0; i < b.N; i++ {
+ if _, err := msg.Pack(); err != nil {
+ b.Fatal("Message.Pack() =", err)
+ }
+ }
+}
+
+func BenchmarkAppendPack(b *testing.B) {
+ msg := largeTestMsg()
+ buf := make([]byte, 0, packStartingCap)
+
+ b.ReportAllocs()
+
+ for i := 0; i < b.N; i++ {
+ if _, err := msg.AppendPack(buf[:0]); err != nil {
+ b.Fatal("Message.AppendPack() = ", err)
+ }
+ }
+}
+
+func largeTestMsg() Message {
+ name := mustNewName("foo.bar.example.com.")
+ return Message{
+ Header: Header{Response: true, Authoritative: true},
+ Questions: []Question{
+ {
+ Name: name,
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ },
+ Answers: []Resource{
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ &AResource{[4]byte{127, 0, 0, 1}},
+ },
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeA,
+ Class: ClassINET,
+ },
+ &AResource{[4]byte{127, 0, 0, 2}},
+ },
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeAAAA,
+ Class: ClassINET,
+ },
+ &AAAAResource{[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}},
+ },
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeCNAME,
+ Class: ClassINET,
+ },
+ &CNAMEResource{mustNewName("alias.example.com.")},
+ },
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeSOA,
+ Class: ClassINET,
+ },
+ &SOAResource{
+ NS: mustNewName("ns1.example.com."),
+ MBox: mustNewName("mb.example.com."),
+ Serial: 1,
+ Refresh: 2,
+ Retry: 3,
+ Expire: 4,
+ MinTTL: 5,
+ },
+ },
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypePTR,
+ Class: ClassINET,
+ },
+ &PTRResource{mustNewName("ptr.example.com.")},
+ },
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeMX,
+ Class: ClassINET,
+ },
+ &MXResource{
+ 7,
+ mustNewName("mx.example.com."),
+ },
+ },
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeSRV,
+ Class: ClassINET,
+ },
+ &SRVResource{
+ 8,
+ 9,
+ 11,
+ mustNewName("srv.example.com."),
+ },
+ },
+ },
+ Authorities: []Resource{
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeNS,
+ Class: ClassINET,
+ },
+ &NSResource{mustNewName("ns1.example.com.")},
+ },
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeNS,
+ Class: ClassINET,
+ },
+ &NSResource{mustNewName("ns2.example.com.")},
+ },
+ },
+ Additionals: []Resource{
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeTXT,
+ Class: ClassINET,
+ },
+ &TXTResource{[]string{"So Long, and Thanks for All the Fish"}},
+ },
+ {
+ ResourceHeader{
+ Name: name,
+ Type: TypeTXT,
+ Class: ClassINET,
+ },
+ &TXTResource{[]string{"Hamster Huey and the Gooey Kablooie"}},
+ },
+ {
+ mustEDNS0ResourceHeader(4096, 0xfe0|RCodeSuccess, false),
+ &OPTResource{
+ Options: []Option{
+ {
+ Code: 10, // see RFC 7873
+ Data: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
+ },
+ },
+ },
+ },
+ },
+ }
+}
diff --git a/vendor/golang.org/x/net/html/atom/atom.go b/vendor/golang.org/x/net/html/atom/atom.go
new file mode 100644
index 0000000..cd0a8ac
--- /dev/null
+++ b/vendor/golang.org/x/net/html/atom/atom.go
@@ -0,0 +1,78 @@
+// Copyright 2012 The Go 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 atom provides integer codes (also known as atoms) for a fixed set of
+// frequently occurring HTML strings: tag names and attribute keys such as "p"
+// and "id".
+//
+// Sharing an atom's name between all elements with the same tag can result in
+// fewer string allocations when tokenizing and parsing HTML. Integer
+// comparisons are also generally faster than string comparisons.
+//
+// The value of an atom's particular code is not guaranteed to stay the same
+// between versions of this package. Neither is any ordering guaranteed:
+// whether atom.H1 < atom.H2 may also change. The codes are not guaranteed to
+// be dense. The only guarantees are that e.g. looking up "div" will yield
+// atom.Div, calling atom.Div.String will return "div", and atom.Div != 0.
+package atom // import "golang.org/x/net/html/atom"
+
+// Atom is an integer code for a string. The zero value maps to "".
+type Atom uint32
+
+// String returns the atom's name.
+func (a Atom) String() string {
+ start := uint32(a >> 8)
+ n := uint32(a & 0xff)
+ if start+n > uint32(len(atomText)) {
+ return ""
+ }
+ return atomText[start : start+n]
+}
+
+func (a Atom) string() string {
+ return atomText[a>>8 : a>>8+a&0xff]
+}
+
+// fnv computes the FNV hash with an arbitrary starting value h.
+func fnv(h uint32, s []byte) uint32 {
+ for i := range s {
+ h ^= uint32(s[i])
+ h *= 16777619
+ }
+ return h
+}
+
+func match(s string, t []byte) bool {
+ for i, c := range t {
+ if s[i] != c {
+ return false
+ }
+ }
+ return true
+}
+
+// Lookup returns the atom whose name is s. It returns zero if there is no
+// such atom. The lookup is case sensitive.
+func Lookup(s []byte) Atom {
+ if len(s) == 0 || len(s) > maxAtomLen {
+ return 0
+ }
+ h := fnv(hash0, s)
+ if a := table[h&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) {
+ return a
+ }
+ if a := table[(h>>16)&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) {
+ return a
+ }
+ return 0
+}
+
+// String returns a string whose contents are equal to s. In that sense, it is
+// equivalent to string(s) but may be more efficient.
+func String(s []byte) string {
+ if a := Lookup(s); a != 0 {
+ return a.String()
+ }
+ return string(s)
+}
diff --git a/vendor/golang.org/x/net/html/atom/atom_test.go b/vendor/golang.org/x/net/html/atom/atom_test.go
new file mode 100644
index 0000000..6e33704
--- /dev/null
+++ b/vendor/golang.org/x/net/html/atom/atom_test.go
@@ -0,0 +1,109 @@
+// Copyright 2012 The Go 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 atom
+
+import (
+ "sort"
+ "testing"
+)
+
+func TestKnown(t *testing.T) {
+ for _, s := range testAtomList {
+ if atom := Lookup([]byte(s)); atom.String() != s {
+ t.Errorf("Lookup(%q) = %#x (%q)", s, uint32(atom), atom.String())
+ }
+ }
+}
+
+func TestHits(t *testing.T) {
+ for _, a := range table {
+ if a == 0 {
+ continue
+ }
+ got := Lookup([]byte(a.String()))
+ if got != a {
+ t.Errorf("Lookup(%q) = %#x, want %#x", a.String(), uint32(got), uint32(a))
+ }
+ }
+}
+
+func TestMisses(t *testing.T) {
+ testCases := []string{
+ "",
+ "\x00",
+ "\xff",
+ "A",
+ "DIV",
+ "Div",
+ "dIV",
+ "aa",
+ "a\x00",
+ "ab",
+ "abb",
+ "abbr0",
+ "abbr ",
+ " abbr",
+ " a",
+ "acceptcharset",
+ "acceptCharset",
+ "accept_charset",
+ "h0",
+ "h1h2",
+ "h7",
+ "onClick",
+ "λ",
+ // The following string has the same hash (0xa1d7fab7) as "onmouseover".
+ "\x00\x00\x00\x00\x00\x50\x18\xae\x38\xd0\xb7",
+ }
+ for _, tc := range testCases {
+ got := Lookup([]byte(tc))
+ if got != 0 {
+ t.Errorf("Lookup(%q): got %d, want 0", tc, got)
+ }
+ }
+}
+
+func TestForeignObject(t *testing.T) {
+ const (
+ afo = Foreignobject
+ afO = ForeignObject
+ sfo = "foreignobject"
+ sfO = "foreignObject"
+ )
+ if got := Lookup([]byte(sfo)); got != afo {
+ t.Errorf("Lookup(%q): got %#v, want %#v", sfo, got, afo)
+ }
+ if got := Lookup([]byte(sfO)); got != afO {
+ t.Errorf("Lookup(%q): got %#v, want %#v", sfO, got, afO)
+ }
+ if got := afo.String(); got != sfo {
+ t.Errorf("Atom(%#v).String(): got %q, want %q", afo, got, sfo)
+ }
+ if got := afO.String(); got != sfO {
+ t.Errorf("Atom(%#v).String(): got %q, want %q", afO, got, sfO)
+ }
+}
+
+func BenchmarkLookup(b *testing.B) {
+ sortedTable := make([]string, 0, len(table))
+ for _, a := range table {
+ if a != 0 {
+ sortedTable = append(sortedTable, a.String())
+ }
+ }
+ sort.Strings(sortedTable)
+
+ x := make([][]byte, 1000)
+ for i := range x {
+ x[i] = []byte(sortedTable[i%len(sortedTable)])
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ for _, s := range x {
+ Lookup(s)
+ }
+ }
+}
diff --git a/vendor/golang.org/x/net/html/atom/gen.go b/vendor/golang.org/x/net/html/atom/gen.go
new file mode 100644
index 0000000..5d05278
--- /dev/null
+++ b/vendor/golang.org/x/net/html/atom/gen.go
@@ -0,0 +1,712 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build ignore
+
+//go:generate go run gen.go
+//go:generate go run gen.go -test
+
+package main
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "go/format"
+ "io/ioutil"
+ "math/rand"
+ "os"
+ "sort"
+ "strings"
+)
+
+// identifier converts s to a Go exported identifier.
+// It converts "div" to "Div" and "accept-charset" to "AcceptCharset".
+func identifier(s string) string {
+ b := make([]byte, 0, len(s))
+ cap := true
+ for _, c := range s {
+ if c == '-' {
+ cap = true
+ continue
+ }
+ if cap && 'a' <= c && c <= 'z' {
+ c -= 'a' - 'A'
+ }
+ cap = false
+ b = append(b, byte(c))
+ }
+ return string(b)
+}
+
+var test = flag.Bool("test", false, "generate table_test.go")
+
+func genFile(name string, buf *bytes.Buffer) {
+ b, err := format.Source(buf.Bytes())
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ if err := ioutil.WriteFile(name, b, 0644); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+}
+
+func main() {
+ flag.Parse()
+
+ var all []string
+ all = append(all, elements...)
+ all = append(all, attributes...)
+ all = append(all, eventHandlers...)
+ all = append(all, extra...)
+ sort.Strings(all)
+
+ // uniq - lists have dups
+ w := 0
+ for _, s := range all {
+ if w == 0 || all[w-1] != s {
+ all[w] = s
+ w++
+ }
+ }
+ all = all[:w]
+
+ if *test {
+ var buf bytes.Buffer
+ fmt.Fprintln(&buf, "// Code generated by go generate gen.go; DO NOT EDIT.\n")
+ fmt.Fprintln(&buf, "//go:generate go run gen.go -test\n")
+ fmt.Fprintln(&buf, "package atom\n")
+ fmt.Fprintln(&buf, "var testAtomList = []string{")
+ for _, s := range all {
+ fmt.Fprintf(&buf, "\t%q,\n", s)
+ }
+ fmt.Fprintln(&buf, "}")
+
+ genFile("table_test.go", &buf)
+ return
+ }
+
+ // Find hash that minimizes table size.
+ var best *table
+ for i := 0; i < 1000000; i++ {
+ if best != nil && 1<<(best.k-1) < len(all) {
+ break
+ }
+ h := rand.Uint32()
+ for k := uint(0); k <= 16; k++ {
+ if best != nil && k >= best.k {
+ break
+ }
+ var t table
+ if t.init(h, k, all) {
+ best = &t
+ break
+ }
+ }
+ }
+ if best == nil {
+ fmt.Fprintf(os.Stderr, "failed to construct string table\n")
+ os.Exit(1)
+ }
+
+ // Lay out strings, using overlaps when possible.
+ layout := append([]string{}, all...)
+
+ // Remove strings that are substrings of other strings
+ for changed := true; changed; {
+ changed = false
+ for i, s := range layout {
+ if s == "" {
+ continue
+ }
+ for j, t := range layout {
+ if i != j && t != "" && strings.Contains(s, t) {
+ changed = true
+ layout[j] = ""
+ }
+ }
+ }
+ }
+
+ // Join strings where one suffix matches another prefix.
+ for {
+ // Find best i, j, k such that layout[i][len-k:] == layout[j][:k],
+ // maximizing overlap length k.
+ besti := -1
+ bestj := -1
+ bestk := 0
+ for i, s := range layout {
+ if s == "" {
+ continue
+ }
+ for j, t := range layout {
+ if i == j {
+ continue
+ }
+ for k := bestk + 1; k <= len(s) && k <= len(t); k++ {
+ if s[len(s)-k:] == t[:k] {
+ besti = i
+ bestj = j
+ bestk = k
+ }
+ }
+ }
+ }
+ if bestk > 0 {
+ layout[besti] += layout[bestj][bestk:]
+ layout[bestj] = ""
+ continue
+ }
+ break
+ }
+
+ text := strings.Join(layout, "")
+
+ atom := map[string]uint32{}
+ for _, s := range all {
+ off := strings.Index(text, s)
+ if off < 0 {
+ panic("lost string " + s)
+ }
+ atom[s] = uint32(off<<8 | len(s))
+ }
+
+ var buf bytes.Buffer
+ // Generate the Go code.
+ fmt.Fprintln(&buf, "// Code generated by go generate gen.go; DO NOT EDIT.\n")
+ fmt.Fprintln(&buf, "//go:generate go run gen.go\n")
+ fmt.Fprintln(&buf, "package atom\n\nconst (")
+
+ // compute max len
+ maxLen := 0
+ for _, s := range all {
+ if maxLen < len(s) {
+ maxLen = len(s)
+ }
+ fmt.Fprintf(&buf, "\t%s Atom = %#x\n", identifier(s), atom[s])
+ }
+ fmt.Fprintln(&buf, ")\n")
+
+ fmt.Fprintf(&buf, "const hash0 = %#x\n\n", best.h0)
+ fmt.Fprintf(&buf, "const maxAtomLen = %d\n\n", maxLen)
+
+ fmt.Fprintf(&buf, "var table = [1<<%d]Atom{\n", best.k)
+ for i, s := range best.tab {
+ if s == "" {
+ continue
+ }
+ fmt.Fprintf(&buf, "\t%#x: %#x, // %s\n", i, atom[s], s)
+ }
+ fmt.Fprintf(&buf, "}\n")
+ datasize := (1 << best.k) * 4
+
+ fmt.Fprintln(&buf, "const atomText =")
+ textsize := len(text)
+ for len(text) > 60 {
+ fmt.Fprintf(&buf, "\t%q +\n", text[:60])
+ text = text[60:]
+ }
+ fmt.Fprintf(&buf, "\t%q\n\n", text)
+
+ genFile("table.go", &buf)
+
+ fmt.Fprintf(os.Stdout, "%d atoms; %d string bytes + %d tables = %d total data\n", len(all), textsize, datasize, textsize+datasize)
+}
+
+type byLen []string
+
+func (x byLen) Less(i, j int) bool { return len(x[i]) > len(x[j]) }
+func (x byLen) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
+func (x byLen) Len() int { return len(x) }
+
+// fnv computes the FNV hash with an arbitrary starting value h.
+func fnv(h uint32, s string) uint32 {
+ for i := 0; i < len(s); i++ {
+ h ^= uint32(s[i])
+ h *= 16777619
+ }
+ return h
+}
+
+// A table represents an attempt at constructing the lookup table.
+// The lookup table uses cuckoo hashing, meaning that each string
+// can be found in one of two positions.
+type table struct {
+ h0 uint32
+ k uint
+ mask uint32
+ tab []string
+}
+
+// hash returns the two hashes for s.
+func (t *table) hash(s string) (h1, h2 uint32) {
+ h := fnv(t.h0, s)
+ h1 = h & t.mask
+ h2 = (h >> 16) & t.mask
+ return
+}
+
+// init initializes the table with the given parameters.
+// h0 is the initial hash value,
+// k is the number of bits of hash value to use, and
+// x is the list of strings to store in the table.
+// init returns false if the table cannot be constructed.
+func (t *table) init(h0 uint32, k uint, x []string) bool {
+ t.h0 = h0
+ t.k = k
+ t.tab = make([]string, 1< len(t.tab) {
+ return false
+ }
+ s := t.tab[i]
+ h1, h2 := t.hash(s)
+ j := h1 + h2 - i
+ if t.tab[j] != "" && !t.push(j, depth+1) {
+ return false
+ }
+ t.tab[j] = s
+ return true
+}
+
+// The lists of element names and attribute keys were taken from
+// https://html.spec.whatwg.org/multipage/indices.html#index
+// as of the "HTML Living Standard - Last Updated 16 April 2018" version.
+
+// "command", "keygen" and "menuitem" have been removed from the spec,
+// but are kept here for backwards compatibility.
+var elements = []string{
+ "a",
+ "abbr",
+ "address",
+ "area",
+ "article",
+ "aside",
+ "audio",
+ "b",
+ "base",
+ "bdi",
+ "bdo",
+ "blockquote",
+ "body",
+ "br",
+ "button",
+ "canvas",
+ "caption",
+ "cite",
+ "code",
+ "col",
+ "colgroup",
+ "command",
+ "data",
+ "datalist",
+ "dd",
+ "del",
+ "details",
+ "dfn",
+ "dialog",
+ "div",
+ "dl",
+ "dt",
+ "em",
+ "embed",
+ "fieldset",
+ "figcaption",
+ "figure",
+ "footer",
+ "form",
+ "h1",
+ "h2",
+ "h3",
+ "h4",
+ "h5",
+ "h6",
+ "head",
+ "header",
+ "hgroup",
+ "hr",
+ "html",
+ "i",
+ "iframe",
+ "img",
+ "input",
+ "ins",
+ "kbd",
+ "keygen",
+ "label",
+ "legend",
+ "li",
+ "link",
+ "main",
+ "map",
+ "mark",
+ "menu",
+ "menuitem",
+ "meta",
+ "meter",
+ "nav",
+ "noscript",
+ "object",
+ "ol",
+ "optgroup",
+ "option",
+ "output",
+ "p",
+ "param",
+ "picture",
+ "pre",
+ "progress",
+ "q",
+ "rp",
+ "rt",
+ "ruby",
+ "s",
+ "samp",
+ "script",
+ "section",
+ "select",
+ "slot",
+ "small",
+ "source",
+ "span",
+ "strong",
+ "style",
+ "sub",
+ "summary",
+ "sup",
+ "table",
+ "tbody",
+ "td",
+ "template",
+ "textarea",
+ "tfoot",
+ "th",
+ "thead",
+ "time",
+ "title",
+ "tr",
+ "track",
+ "u",
+ "ul",
+ "var",
+ "video",
+ "wbr",
+}
+
+// https://html.spec.whatwg.org/multipage/indices.html#attributes-3
+//
+// "challenge", "command", "contextmenu", "dropzone", "icon", "keytype", "mediagroup",
+// "radiogroup", "spellcheck", "scoped", "seamless", "sortable" and "sorted" have been removed from the spec,
+// but are kept here for backwards compatibility.
+var attributes = []string{
+ "abbr",
+ "accept",
+ "accept-charset",
+ "accesskey",
+ "action",
+ "allowfullscreen",
+ "allowpaymentrequest",
+ "allowusermedia",
+ "alt",
+ "as",
+ "async",
+ "autocomplete",
+ "autofocus",
+ "autoplay",
+ "challenge",
+ "charset",
+ "checked",
+ "cite",
+ "class",
+ "color",
+ "cols",
+ "colspan",
+ "command",
+ "content",
+ "contenteditable",
+ "contextmenu",
+ "controls",
+ "coords",
+ "crossorigin",
+ "data",
+ "datetime",
+ "default",
+ "defer",
+ "dir",
+ "dirname",
+ "disabled",
+ "download",
+ "draggable",
+ "dropzone",
+ "enctype",
+ "for",
+ "form",
+ "formaction",
+ "formenctype",
+ "formmethod",
+ "formnovalidate",
+ "formtarget",
+ "headers",
+ "height",
+ "hidden",
+ "high",
+ "href",
+ "hreflang",
+ "http-equiv",
+ "icon",
+ "id",
+ "inputmode",
+ "integrity",
+ "is",
+ "ismap",
+ "itemid",
+ "itemprop",
+ "itemref",
+ "itemscope",
+ "itemtype",
+ "keytype",
+ "kind",
+ "label",
+ "lang",
+ "list",
+ "loop",
+ "low",
+ "manifest",
+ "max",
+ "maxlength",
+ "media",
+ "mediagroup",
+ "method",
+ "min",
+ "minlength",
+ "multiple",
+ "muted",
+ "name",
+ "nomodule",
+ "nonce",
+ "novalidate",
+ "open",
+ "optimum",
+ "pattern",
+ "ping",
+ "placeholder",
+ "playsinline",
+ "poster",
+ "preload",
+ "radiogroup",
+ "readonly",
+ "referrerpolicy",
+ "rel",
+ "required",
+ "reversed",
+ "rows",
+ "rowspan",
+ "sandbox",
+ "spellcheck",
+ "scope",
+ "scoped",
+ "seamless",
+ "selected",
+ "shape",
+ "size",
+ "sizes",
+ "sortable",
+ "sorted",
+ "slot",
+ "span",
+ "spellcheck",
+ "src",
+ "srcdoc",
+ "srclang",
+ "srcset",
+ "start",
+ "step",
+ "style",
+ "tabindex",
+ "target",
+ "title",
+ "translate",
+ "type",
+ "typemustmatch",
+ "updateviacache",
+ "usemap",
+ "value",
+ "width",
+ "workertype",
+ "wrap",
+}
+
+// "onautocomplete", "onautocompleteerror", "onmousewheel",
+// "onshow" and "onsort" have been removed from the spec,
+// but are kept here for backwards compatibility.
+var eventHandlers = []string{
+ "onabort",
+ "onautocomplete",
+ "onautocompleteerror",
+ "onauxclick",
+ "onafterprint",
+ "onbeforeprint",
+ "onbeforeunload",
+ "onblur",
+ "oncancel",
+ "oncanplay",
+ "oncanplaythrough",
+ "onchange",
+ "onclick",
+ "onclose",
+ "oncontextmenu",
+ "oncopy",
+ "oncuechange",
+ "oncut",
+ "ondblclick",
+ "ondrag",
+ "ondragend",
+ "ondragenter",
+ "ondragexit",
+ "ondragleave",
+ "ondragover",
+ "ondragstart",
+ "ondrop",
+ "ondurationchange",
+ "onemptied",
+ "onended",
+ "onerror",
+ "onfocus",
+ "onhashchange",
+ "oninput",
+ "oninvalid",
+ "onkeydown",
+ "onkeypress",
+ "onkeyup",
+ "onlanguagechange",
+ "onload",
+ "onloadeddata",
+ "onloadedmetadata",
+ "onloadend",
+ "onloadstart",
+ "onmessage",
+ "onmessageerror",
+ "onmousedown",
+ "onmouseenter",
+ "onmouseleave",
+ "onmousemove",
+ "onmouseout",
+ "onmouseover",
+ "onmouseup",
+ "onmousewheel",
+ "onwheel",
+ "onoffline",
+ "ononline",
+ "onpagehide",
+ "onpageshow",
+ "onpaste",
+ "onpause",
+ "onplay",
+ "onplaying",
+ "onpopstate",
+ "onprogress",
+ "onratechange",
+ "onreset",
+ "onresize",
+ "onrejectionhandled",
+ "onscroll",
+ "onsecuritypolicyviolation",
+ "onseeked",
+ "onseeking",
+ "onselect",
+ "onshow",
+ "onsort",
+ "onstalled",
+ "onstorage",
+ "onsubmit",
+ "onsuspend",
+ "ontimeupdate",
+ "ontoggle",
+ "onunhandledrejection",
+ "onunload",
+ "onvolumechange",
+ "onwaiting",
+}
+
+// extra are ad-hoc values not covered by any of the lists above.
+var extra = []string{
+ "acronym",
+ "align",
+ "annotation",
+ "annotation-xml",
+ "applet",
+ "basefont",
+ "bgsound",
+ "big",
+ "blink",
+ "center",
+ "color",
+ "desc",
+ "face",
+ "font",
+ "foreignObject", // HTML is case-insensitive, but SVG-embedded-in-HTML is case-sensitive.
+ "foreignobject",
+ "frame",
+ "frameset",
+ "image",
+ "isindex",
+ "listing",
+ "malignmark",
+ "marquee",
+ "math",
+ "mglyph",
+ "mi",
+ "mn",
+ "mo",
+ "ms",
+ "mtext",
+ "nobr",
+ "noembed",
+ "noframes",
+ "plaintext",
+ "prompt",
+ "public",
+ "rb",
+ "rtc",
+ "spacer",
+ "strike",
+ "svg",
+ "system",
+ "tt",
+ "xmp",
+}
diff --git a/vendor/golang.org/x/net/html/atom/table.go b/vendor/golang.org/x/net/html/atom/table.go
new file mode 100644
index 0000000..2a93886
--- /dev/null
+++ b/vendor/golang.org/x/net/html/atom/table.go
@@ -0,0 +1,783 @@
+// Code generated by go generate gen.go; DO NOT EDIT.
+
+//go:generate go run gen.go
+
+package atom
+
+const (
+ A Atom = 0x1
+ Abbr Atom = 0x4
+ Accept Atom = 0x1a06
+ AcceptCharset Atom = 0x1a0e
+ Accesskey Atom = 0x2c09
+ Acronym Atom = 0xaa07
+ Action Atom = 0x27206
+ Address Atom = 0x6f307
+ Align Atom = 0xb105
+ Allowfullscreen Atom = 0x2080f
+ Allowpaymentrequest Atom = 0xc113
+ Allowusermedia Atom = 0xdd0e
+ Alt Atom = 0xf303
+ Annotation Atom = 0x1c90a
+ AnnotationXml Atom = 0x1c90e
+ Applet Atom = 0x31906
+ Area Atom = 0x35604
+ Article Atom = 0x3fc07
+ As Atom = 0x3c02
+ Aside Atom = 0x10705
+ Async Atom = 0xff05
+ Audio Atom = 0x11505
+ Autocomplete Atom = 0x2780c
+ Autofocus Atom = 0x12109
+ Autoplay Atom = 0x13c08
+ B Atom = 0x101
+ Base Atom = 0x3b04
+ Basefont Atom = 0x3b08
+ Bdi Atom = 0xba03
+ Bdo Atom = 0x14b03
+ Bgsound Atom = 0x15e07
+ Big Atom = 0x17003
+ Blink Atom = 0x17305
+ Blockquote Atom = 0x1870a
+ Body Atom = 0x2804
+ Br Atom = 0x202
+ Button Atom = 0x19106
+ Canvas Atom = 0x10306
+ Caption Atom = 0x23107
+ Center Atom = 0x22006
+ Challenge Atom = 0x29b09
+ Charset Atom = 0x2107
+ Checked Atom = 0x47907
+ Cite Atom = 0x19c04
+ Class Atom = 0x56405
+ Code Atom = 0x5c504
+ Col Atom = 0x1ab03
+ Colgroup Atom = 0x1ab08
+ Color Atom = 0x1bf05
+ Cols Atom = 0x1c404
+ Colspan Atom = 0x1c407
+ Command Atom = 0x1d707
+ Content Atom = 0x58b07
+ Contenteditable Atom = 0x58b0f
+ Contextmenu Atom = 0x3800b
+ Controls Atom = 0x1de08
+ Coords Atom = 0x1ea06
+ Crossorigin Atom = 0x1fb0b
+ Data Atom = 0x4a504
+ Datalist Atom = 0x4a508
+ Datetime Atom = 0x2b808
+ Dd Atom = 0x2d702
+ Default Atom = 0x10a07
+ Defer Atom = 0x5c705
+ Del Atom = 0x45203
+ Desc Atom = 0x56104
+ Details Atom = 0x7207
+ Dfn Atom = 0x8703
+ Dialog Atom = 0xbb06
+ Dir Atom = 0x9303
+ Dirname Atom = 0x9307
+ Disabled Atom = 0x16408
+ Div Atom = 0x16b03
+ Dl Atom = 0x5e602
+ Download Atom = 0x46308
+ Draggable Atom = 0x17a09
+ Dropzone Atom = 0x40508
+ Dt Atom = 0x64b02
+ Em Atom = 0x6e02
+ Embed Atom = 0x6e05
+ Enctype Atom = 0x28d07
+ Face Atom = 0x21e04
+ Fieldset Atom = 0x22608
+ Figcaption Atom = 0x22e0a
+ Figure Atom = 0x24806
+ Font Atom = 0x3f04
+ Footer Atom = 0xf606
+ For Atom = 0x25403
+ ForeignObject Atom = 0x2540d
+ Foreignobject Atom = 0x2610d
+ Form Atom = 0x26e04
+ Formaction Atom = 0x26e0a
+ Formenctype Atom = 0x2890b
+ Formmethod Atom = 0x2a40a
+ Formnovalidate Atom = 0x2ae0e
+ Formtarget Atom = 0x2c00a
+ Frame Atom = 0x8b05
+ Frameset Atom = 0x8b08
+ H1 Atom = 0x15c02
+ H2 Atom = 0x2de02
+ H3 Atom = 0x30d02
+ H4 Atom = 0x34502
+ H5 Atom = 0x34f02
+ H6 Atom = 0x64d02
+ Head Atom = 0x33104
+ Header Atom = 0x33106
+ Headers Atom = 0x33107
+ Height Atom = 0x5206
+ Hgroup Atom = 0x2ca06
+ Hidden Atom = 0x2d506
+ High Atom = 0x2db04
+ Hr Atom = 0x15702
+ Href Atom = 0x2e004
+ Hreflang Atom = 0x2e008
+ Html Atom = 0x5604
+ HttpEquiv Atom = 0x2e80a
+ I Atom = 0x601
+ Icon Atom = 0x58a04
+ Id Atom = 0x10902
+ Iframe Atom = 0x2fc06
+ Image Atom = 0x30205
+ Img Atom = 0x30703
+ Input Atom = 0x44b05
+ Inputmode Atom = 0x44b09
+ Ins Atom = 0x20403
+ Integrity Atom = 0x23f09
+ Is Atom = 0x16502
+ Isindex Atom = 0x30f07
+ Ismap Atom = 0x31605
+ Itemid Atom = 0x38b06
+ Itemprop Atom = 0x19d08
+ Itemref Atom = 0x3cd07
+ Itemscope Atom = 0x67109
+ Itemtype Atom = 0x31f08
+ Kbd Atom = 0xb903
+ Keygen Atom = 0x3206
+ Keytype Atom = 0xd607
+ Kind Atom = 0x17704
+ Label Atom = 0x5905
+ Lang Atom = 0x2e404
+ Legend Atom = 0x18106
+ Li Atom = 0xb202
+ Link Atom = 0x17404
+ List Atom = 0x4a904
+ Listing Atom = 0x4a907
+ Loop Atom = 0x5d04
+ Low Atom = 0xc303
+ Main Atom = 0x1004
+ Malignmark Atom = 0xb00a
+ Manifest Atom = 0x6d708
+ Map Atom = 0x31803
+ Mark Atom = 0xb604
+ Marquee Atom = 0x32707
+ Math Atom = 0x32e04
+ Max Atom = 0x33d03
+ Maxlength Atom = 0x33d09
+ Media Atom = 0xe605
+ Mediagroup Atom = 0xe60a
+ Menu Atom = 0x38704
+ Menuitem Atom = 0x38708
+ Meta Atom = 0x4b804
+ Meter Atom = 0x9805
+ Method Atom = 0x2a806
+ Mglyph Atom = 0x30806
+ Mi Atom = 0x34702
+ Min Atom = 0x34703
+ Minlength Atom = 0x34709
+ Mn Atom = 0x2b102
+ Mo Atom = 0xa402
+ Ms Atom = 0x67402
+ Mtext Atom = 0x35105
+ Multiple Atom = 0x35f08
+ Muted Atom = 0x36705
+ Name Atom = 0x9604
+ Nav Atom = 0x1303
+ Nobr Atom = 0x3704
+ Noembed Atom = 0x6c07
+ Noframes Atom = 0x8908
+ Nomodule Atom = 0xa208
+ Nonce Atom = 0x1a605
+ Noscript Atom = 0x21608
+ Novalidate Atom = 0x2b20a
+ Object Atom = 0x26806
+ Ol Atom = 0x13702
+ Onabort Atom = 0x19507
+ Onafterprint Atom = 0x2360c
+ Onautocomplete Atom = 0x2760e
+ Onautocompleteerror Atom = 0x27613
+ Onauxclick Atom = 0x61f0a
+ Onbeforeprint Atom = 0x69e0d
+ Onbeforeunload Atom = 0x6e70e
+ Onblur Atom = 0x56d06
+ Oncancel Atom = 0x11908
+ Oncanplay Atom = 0x14d09
+ Oncanplaythrough Atom = 0x14d10
+ Onchange Atom = 0x41b08
+ Onclick Atom = 0x2f507
+ Onclose Atom = 0x36c07
+ Oncontextmenu Atom = 0x37e0d
+ Oncopy Atom = 0x39106
+ Oncuechange Atom = 0x3970b
+ Oncut Atom = 0x3a205
+ Ondblclick Atom = 0x3a70a
+ Ondrag Atom = 0x3b106
+ Ondragend Atom = 0x3b109
+ Ondragenter Atom = 0x3ba0b
+ Ondragexit Atom = 0x3c50a
+ Ondragleave Atom = 0x3df0b
+ Ondragover Atom = 0x3ea0a
+ Ondragstart Atom = 0x3f40b
+ Ondrop Atom = 0x40306
+ Ondurationchange Atom = 0x41310
+ Onemptied Atom = 0x40a09
+ Onended Atom = 0x42307
+ Onerror Atom = 0x42a07
+ Onfocus Atom = 0x43107
+ Onhashchange Atom = 0x43d0c
+ Oninput Atom = 0x44907
+ Oninvalid Atom = 0x45509
+ Onkeydown Atom = 0x45e09
+ Onkeypress Atom = 0x46b0a
+ Onkeyup Atom = 0x48007
+ Onlanguagechange Atom = 0x48d10
+ Onload Atom = 0x49d06
+ Onloadeddata Atom = 0x49d0c
+ Onloadedmetadata Atom = 0x4b010
+ Onloadend Atom = 0x4c609
+ Onloadstart Atom = 0x4cf0b
+ Onmessage Atom = 0x4da09
+ Onmessageerror Atom = 0x4da0e
+ Onmousedown Atom = 0x4e80b
+ Onmouseenter Atom = 0x4f30c
+ Onmouseleave Atom = 0x4ff0c
+ Onmousemove Atom = 0x50b0b
+ Onmouseout Atom = 0x5160a
+ Onmouseover Atom = 0x5230b
+ Onmouseup Atom = 0x52e09
+ Onmousewheel Atom = 0x53c0c
+ Onoffline Atom = 0x54809
+ Ononline Atom = 0x55108
+ Onpagehide Atom = 0x5590a
+ Onpageshow Atom = 0x5730a
+ Onpaste Atom = 0x57f07
+ Onpause Atom = 0x59a07
+ Onplay Atom = 0x5a406
+ Onplaying Atom = 0x5a409
+ Onpopstate Atom = 0x5ad0a
+ Onprogress Atom = 0x5b70a
+ Onratechange Atom = 0x5cc0c
+ Onrejectionhandled Atom = 0x5d812
+ Onreset Atom = 0x5ea07
+ Onresize Atom = 0x5f108
+ Onscroll Atom = 0x60008
+ Onsecuritypolicyviolation Atom = 0x60819
+ Onseeked Atom = 0x62908
+ Onseeking Atom = 0x63109
+ Onselect Atom = 0x63a08
+ Onshow Atom = 0x64406
+ Onsort Atom = 0x64f06
+ Onstalled Atom = 0x65909
+ Onstorage Atom = 0x66209
+ Onsubmit Atom = 0x66b08
+ Onsuspend Atom = 0x67b09
+ Ontimeupdate Atom = 0x400c
+ Ontoggle Atom = 0x68408
+ Onunhandledrejection Atom = 0x68c14
+ Onunload Atom = 0x6ab08
+ Onvolumechange Atom = 0x6b30e
+ Onwaiting Atom = 0x6c109
+ Onwheel Atom = 0x6ca07
+ Open Atom = 0x1a304
+ Optgroup Atom = 0x5f08
+ Optimum Atom = 0x6d107
+ Option Atom = 0x6e306
+ Output Atom = 0x51d06
+ P Atom = 0xc01
+ Param Atom = 0xc05
+ Pattern Atom = 0x6607
+ Picture Atom = 0x7b07
+ Ping Atom = 0xef04
+ Placeholder Atom = 0x1310b
+ Plaintext Atom = 0x1b209
+ Playsinline Atom = 0x1400b
+ Poster Atom = 0x2cf06
+ Pre Atom = 0x47003
+ Preload Atom = 0x48607
+ Progress Atom = 0x5b908
+ Prompt Atom = 0x53606
+ Public Atom = 0x58606
+ Q Atom = 0xcf01
+ Radiogroup Atom = 0x30a
+ Rb Atom = 0x3a02
+ Readonly Atom = 0x35708
+ Referrerpolicy Atom = 0x3d10e
+ Rel Atom = 0x48703
+ Required Atom = 0x24c08
+ Reversed Atom = 0x8008
+ Rows Atom = 0x9c04
+ Rowspan Atom = 0x9c07
+ Rp Atom = 0x23c02
+ Rt Atom = 0x19a02
+ Rtc Atom = 0x19a03
+ Ruby Atom = 0xfb04
+ S Atom = 0x2501
+ Samp Atom = 0x7804
+ Sandbox Atom = 0x12907
+ Scope Atom = 0x67505
+ Scoped Atom = 0x67506
+ Script Atom = 0x21806
+ Seamless Atom = 0x37108
+ Section Atom = 0x56807
+ Select Atom = 0x63c06
+ Selected Atom = 0x63c08
+ Shape Atom = 0x1e505
+ Size Atom = 0x5f504
+ Sizes Atom = 0x5f505
+ Slot Atom = 0x1ef04
+ Small Atom = 0x20605
+ Sortable Atom = 0x65108
+ Sorted Atom = 0x33706
+ Source Atom = 0x37806
+ Spacer Atom = 0x43706
+ Span Atom = 0x9f04
+ Spellcheck Atom = 0x4740a
+ Src Atom = 0x5c003
+ Srcdoc Atom = 0x5c006
+ Srclang Atom = 0x5f907
+ Srcset Atom = 0x6f906
+ Start Atom = 0x3fa05
+ Step Atom = 0x58304
+ Strike Atom = 0xd206
+ Strong Atom = 0x6dd06
+ Style Atom = 0x6ff05
+ Sub Atom = 0x66d03
+ Summary Atom = 0x70407
+ Sup Atom = 0x70b03
+ Svg Atom = 0x70e03
+ System Atom = 0x71106
+ Tabindex Atom = 0x4be08
+ Table Atom = 0x59505
+ Target Atom = 0x2c406
+ Tbody Atom = 0x2705
+ Td Atom = 0x9202
+ Template Atom = 0x71408
+ Textarea Atom = 0x35208
+ Tfoot Atom = 0xf505
+ Th Atom = 0x15602
+ Thead Atom = 0x33005
+ Time Atom = 0x4204
+ Title Atom = 0x11005
+ Tr Atom = 0xcc02
+ Track Atom = 0x1ba05
+ Translate Atom = 0x1f209
+ Tt Atom = 0x6802
+ Type Atom = 0xd904
+ Typemustmatch Atom = 0x2900d
+ U Atom = 0xb01
+ Ul Atom = 0xa702
+ Updateviacache Atom = 0x460e
+ Usemap Atom = 0x59e06
+ Value Atom = 0x1505
+ Var Atom = 0x16d03
+ Video Atom = 0x2f105
+ Wbr Atom = 0x57c03
+ Width Atom = 0x64905
+ Workertype Atom = 0x71c0a
+ Wrap Atom = 0x72604
+ Xmp Atom = 0x12f03
+)
+
+const hash0 = 0x81cdf10e
+
+const maxAtomLen = 25
+
+var table = [1 << 9]Atom{
+ 0x1: 0xe60a, // mediagroup
+ 0x2: 0x2e404, // lang
+ 0x4: 0x2c09, // accesskey
+ 0x5: 0x8b08, // frameset
+ 0x7: 0x63a08, // onselect
+ 0x8: 0x71106, // system
+ 0xa: 0x64905, // width
+ 0xc: 0x2890b, // formenctype
+ 0xd: 0x13702, // ol
+ 0xe: 0x3970b, // oncuechange
+ 0x10: 0x14b03, // bdo
+ 0x11: 0x11505, // audio
+ 0x12: 0x17a09, // draggable
+ 0x14: 0x2f105, // video
+ 0x15: 0x2b102, // mn
+ 0x16: 0x38704, // menu
+ 0x17: 0x2cf06, // poster
+ 0x19: 0xf606, // footer
+ 0x1a: 0x2a806, // method
+ 0x1b: 0x2b808, // datetime
+ 0x1c: 0x19507, // onabort
+ 0x1d: 0x460e, // updateviacache
+ 0x1e: 0xff05, // async
+ 0x1f: 0x49d06, // onload
+ 0x21: 0x11908, // oncancel
+ 0x22: 0x62908, // onseeked
+ 0x23: 0x30205, // image
+ 0x24: 0x5d812, // onrejectionhandled
+ 0x26: 0x17404, // link
+ 0x27: 0x51d06, // output
+ 0x28: 0x33104, // head
+ 0x29: 0x4ff0c, // onmouseleave
+ 0x2a: 0x57f07, // onpaste
+ 0x2b: 0x5a409, // onplaying
+ 0x2c: 0x1c407, // colspan
+ 0x2f: 0x1bf05, // color
+ 0x30: 0x5f504, // size
+ 0x31: 0x2e80a, // http-equiv
+ 0x33: 0x601, // i
+ 0x34: 0x5590a, // onpagehide
+ 0x35: 0x68c14, // onunhandledrejection
+ 0x37: 0x42a07, // onerror
+ 0x3a: 0x3b08, // basefont
+ 0x3f: 0x1303, // nav
+ 0x40: 0x17704, // kind
+ 0x41: 0x35708, // readonly
+ 0x42: 0x30806, // mglyph
+ 0x44: 0xb202, // li
+ 0x46: 0x2d506, // hidden
+ 0x47: 0x70e03, // svg
+ 0x48: 0x58304, // step
+ 0x49: 0x23f09, // integrity
+ 0x4a: 0x58606, // public
+ 0x4c: 0x1ab03, // col
+ 0x4d: 0x1870a, // blockquote
+ 0x4e: 0x34f02, // h5
+ 0x50: 0x5b908, // progress
+ 0x51: 0x5f505, // sizes
+ 0x52: 0x34502, // h4
+ 0x56: 0x33005, // thead
+ 0x57: 0xd607, // keytype
+ 0x58: 0x5b70a, // onprogress
+ 0x59: 0x44b09, // inputmode
+ 0x5a: 0x3b109, // ondragend
+ 0x5d: 0x3a205, // oncut
+ 0x5e: 0x43706, // spacer
+ 0x5f: 0x1ab08, // colgroup
+ 0x62: 0x16502, // is
+ 0x65: 0x3c02, // as
+ 0x66: 0x54809, // onoffline
+ 0x67: 0x33706, // sorted
+ 0x69: 0x48d10, // onlanguagechange
+ 0x6c: 0x43d0c, // onhashchange
+ 0x6d: 0x9604, // name
+ 0x6e: 0xf505, // tfoot
+ 0x6f: 0x56104, // desc
+ 0x70: 0x33d03, // max
+ 0x72: 0x1ea06, // coords
+ 0x73: 0x30d02, // h3
+ 0x74: 0x6e70e, // onbeforeunload
+ 0x75: 0x9c04, // rows
+ 0x76: 0x63c06, // select
+ 0x77: 0x9805, // meter
+ 0x78: 0x38b06, // itemid
+ 0x79: 0x53c0c, // onmousewheel
+ 0x7a: 0x5c006, // srcdoc
+ 0x7d: 0x1ba05, // track
+ 0x7f: 0x31f08, // itemtype
+ 0x82: 0xa402, // mo
+ 0x83: 0x41b08, // onchange
+ 0x84: 0x33107, // headers
+ 0x85: 0x5cc0c, // onratechange
+ 0x86: 0x60819, // onsecuritypolicyviolation
+ 0x88: 0x4a508, // datalist
+ 0x89: 0x4e80b, // onmousedown
+ 0x8a: 0x1ef04, // slot
+ 0x8b: 0x4b010, // onloadedmetadata
+ 0x8c: 0x1a06, // accept
+ 0x8d: 0x26806, // object
+ 0x91: 0x6b30e, // onvolumechange
+ 0x92: 0x2107, // charset
+ 0x93: 0x27613, // onautocompleteerror
+ 0x94: 0xc113, // allowpaymentrequest
+ 0x95: 0x2804, // body
+ 0x96: 0x10a07, // default
+ 0x97: 0x63c08, // selected
+ 0x98: 0x21e04, // face
+ 0x99: 0x1e505, // shape
+ 0x9b: 0x68408, // ontoggle
+ 0x9e: 0x64b02, // dt
+ 0x9f: 0xb604, // mark
+ 0xa1: 0xb01, // u
+ 0xa4: 0x6ab08, // onunload
+ 0xa5: 0x5d04, // loop
+ 0xa6: 0x16408, // disabled
+ 0xaa: 0x42307, // onended
+ 0xab: 0xb00a, // malignmark
+ 0xad: 0x67b09, // onsuspend
+ 0xae: 0x35105, // mtext
+ 0xaf: 0x64f06, // onsort
+ 0xb0: 0x19d08, // itemprop
+ 0xb3: 0x67109, // itemscope
+ 0xb4: 0x17305, // blink
+ 0xb6: 0x3b106, // ondrag
+ 0xb7: 0xa702, // ul
+ 0xb8: 0x26e04, // form
+ 0xb9: 0x12907, // sandbox
+ 0xba: 0x8b05, // frame
+ 0xbb: 0x1505, // value
+ 0xbc: 0x66209, // onstorage
+ 0xbf: 0xaa07, // acronym
+ 0xc0: 0x19a02, // rt
+ 0xc2: 0x202, // br
+ 0xc3: 0x22608, // fieldset
+ 0xc4: 0x2900d, // typemustmatch
+ 0xc5: 0xa208, // nomodule
+ 0xc6: 0x6c07, // noembed
+ 0xc7: 0x69e0d, // onbeforeprint
+ 0xc8: 0x19106, // button
+ 0xc9: 0x2f507, // onclick
+ 0xca: 0x70407, // summary
+ 0xcd: 0xfb04, // ruby
+ 0xce: 0x56405, // class
+ 0xcf: 0x3f40b, // ondragstart
+ 0xd0: 0x23107, // caption
+ 0xd4: 0xdd0e, // allowusermedia
+ 0xd5: 0x4cf0b, // onloadstart
+ 0xd9: 0x16b03, // div
+ 0xda: 0x4a904, // list
+ 0xdb: 0x32e04, // math
+ 0xdc: 0x44b05, // input
+ 0xdf: 0x3ea0a, // ondragover
+ 0xe0: 0x2de02, // h2
+ 0xe2: 0x1b209, // plaintext
+ 0xe4: 0x4f30c, // onmouseenter
+ 0xe7: 0x47907, // checked
+ 0xe8: 0x47003, // pre
+ 0xea: 0x35f08, // multiple
+ 0xeb: 0xba03, // bdi
+ 0xec: 0x33d09, // maxlength
+ 0xed: 0xcf01, // q
+ 0xee: 0x61f0a, // onauxclick
+ 0xf0: 0x57c03, // wbr
+ 0xf2: 0x3b04, // base
+ 0xf3: 0x6e306, // option
+ 0xf5: 0x41310, // ondurationchange
+ 0xf7: 0x8908, // noframes
+ 0xf9: 0x40508, // dropzone
+ 0xfb: 0x67505, // scope
+ 0xfc: 0x8008, // reversed
+ 0xfd: 0x3ba0b, // ondragenter
+ 0xfe: 0x3fa05, // start
+ 0xff: 0x12f03, // xmp
+ 0x100: 0x5f907, // srclang
+ 0x101: 0x30703, // img
+ 0x104: 0x101, // b
+ 0x105: 0x25403, // for
+ 0x106: 0x10705, // aside
+ 0x107: 0x44907, // oninput
+ 0x108: 0x35604, // area
+ 0x109: 0x2a40a, // formmethod
+ 0x10a: 0x72604, // wrap
+ 0x10c: 0x23c02, // rp
+ 0x10d: 0x46b0a, // onkeypress
+ 0x10e: 0x6802, // tt
+ 0x110: 0x34702, // mi
+ 0x111: 0x36705, // muted
+ 0x112: 0xf303, // alt
+ 0x113: 0x5c504, // code
+ 0x114: 0x6e02, // em
+ 0x115: 0x3c50a, // ondragexit
+ 0x117: 0x9f04, // span
+ 0x119: 0x6d708, // manifest
+ 0x11a: 0x38708, // menuitem
+ 0x11b: 0x58b07, // content
+ 0x11d: 0x6c109, // onwaiting
+ 0x11f: 0x4c609, // onloadend
+ 0x121: 0x37e0d, // oncontextmenu
+ 0x123: 0x56d06, // onblur
+ 0x124: 0x3fc07, // article
+ 0x125: 0x9303, // dir
+ 0x126: 0xef04, // ping
+ 0x127: 0x24c08, // required
+ 0x128: 0x45509, // oninvalid
+ 0x129: 0xb105, // align
+ 0x12b: 0x58a04, // icon
+ 0x12c: 0x64d02, // h6
+ 0x12d: 0x1c404, // cols
+ 0x12e: 0x22e0a, // figcaption
+ 0x12f: 0x45e09, // onkeydown
+ 0x130: 0x66b08, // onsubmit
+ 0x131: 0x14d09, // oncanplay
+ 0x132: 0x70b03, // sup
+ 0x133: 0xc01, // p
+ 0x135: 0x40a09, // onemptied
+ 0x136: 0x39106, // oncopy
+ 0x137: 0x19c04, // cite
+ 0x138: 0x3a70a, // ondblclick
+ 0x13a: 0x50b0b, // onmousemove
+ 0x13c: 0x66d03, // sub
+ 0x13d: 0x48703, // rel
+ 0x13e: 0x5f08, // optgroup
+ 0x142: 0x9c07, // rowspan
+ 0x143: 0x37806, // source
+ 0x144: 0x21608, // noscript
+ 0x145: 0x1a304, // open
+ 0x146: 0x20403, // ins
+ 0x147: 0x2540d, // foreignObject
+ 0x148: 0x5ad0a, // onpopstate
+ 0x14a: 0x28d07, // enctype
+ 0x14b: 0x2760e, // onautocomplete
+ 0x14c: 0x35208, // textarea
+ 0x14e: 0x2780c, // autocomplete
+ 0x14f: 0x15702, // hr
+ 0x150: 0x1de08, // controls
+ 0x151: 0x10902, // id
+ 0x153: 0x2360c, // onafterprint
+ 0x155: 0x2610d, // foreignobject
+ 0x156: 0x32707, // marquee
+ 0x157: 0x59a07, // onpause
+ 0x158: 0x5e602, // dl
+ 0x159: 0x5206, // height
+ 0x15a: 0x34703, // min
+ 0x15b: 0x9307, // dirname
+ 0x15c: 0x1f209, // translate
+ 0x15d: 0x5604, // html
+ 0x15e: 0x34709, // minlength
+ 0x15f: 0x48607, // preload
+ 0x160: 0x71408, // template
+ 0x161: 0x3df0b, // ondragleave
+ 0x162: 0x3a02, // rb
+ 0x164: 0x5c003, // src
+ 0x165: 0x6dd06, // strong
+ 0x167: 0x7804, // samp
+ 0x168: 0x6f307, // address
+ 0x169: 0x55108, // ononline
+ 0x16b: 0x1310b, // placeholder
+ 0x16c: 0x2c406, // target
+ 0x16d: 0x20605, // small
+ 0x16e: 0x6ca07, // onwheel
+ 0x16f: 0x1c90a, // annotation
+ 0x170: 0x4740a, // spellcheck
+ 0x171: 0x7207, // details
+ 0x172: 0x10306, // canvas
+ 0x173: 0x12109, // autofocus
+ 0x174: 0xc05, // param
+ 0x176: 0x46308, // download
+ 0x177: 0x45203, // del
+ 0x178: 0x36c07, // onclose
+ 0x179: 0xb903, // kbd
+ 0x17a: 0x31906, // applet
+ 0x17b: 0x2e004, // href
+ 0x17c: 0x5f108, // onresize
+ 0x17e: 0x49d0c, // onloadeddata
+ 0x180: 0xcc02, // tr
+ 0x181: 0x2c00a, // formtarget
+ 0x182: 0x11005, // title
+ 0x183: 0x6ff05, // style
+ 0x184: 0xd206, // strike
+ 0x185: 0x59e06, // usemap
+ 0x186: 0x2fc06, // iframe
+ 0x187: 0x1004, // main
+ 0x189: 0x7b07, // picture
+ 0x18c: 0x31605, // ismap
+ 0x18e: 0x4a504, // data
+ 0x18f: 0x5905, // label
+ 0x191: 0x3d10e, // referrerpolicy
+ 0x192: 0x15602, // th
+ 0x194: 0x53606, // prompt
+ 0x195: 0x56807, // section
+ 0x197: 0x6d107, // optimum
+ 0x198: 0x2db04, // high
+ 0x199: 0x15c02, // h1
+ 0x19a: 0x65909, // onstalled
+ 0x19b: 0x16d03, // var
+ 0x19c: 0x4204, // time
+ 0x19e: 0x67402, // ms
+ 0x19f: 0x33106, // header
+ 0x1a0: 0x4da09, // onmessage
+ 0x1a1: 0x1a605, // nonce
+ 0x1a2: 0x26e0a, // formaction
+ 0x1a3: 0x22006, // center
+ 0x1a4: 0x3704, // nobr
+ 0x1a5: 0x59505, // table
+ 0x1a6: 0x4a907, // listing
+ 0x1a7: 0x18106, // legend
+ 0x1a9: 0x29b09, // challenge
+ 0x1aa: 0x24806, // figure
+ 0x1ab: 0xe605, // media
+ 0x1ae: 0xd904, // type
+ 0x1af: 0x3f04, // font
+ 0x1b0: 0x4da0e, // onmessageerror
+ 0x1b1: 0x37108, // seamless
+ 0x1b2: 0x8703, // dfn
+ 0x1b3: 0x5c705, // defer
+ 0x1b4: 0xc303, // low
+ 0x1b5: 0x19a03, // rtc
+ 0x1b6: 0x5230b, // onmouseover
+ 0x1b7: 0x2b20a, // novalidate
+ 0x1b8: 0x71c0a, // workertype
+ 0x1ba: 0x3cd07, // itemref
+ 0x1bd: 0x1, // a
+ 0x1be: 0x31803, // map
+ 0x1bf: 0x400c, // ontimeupdate
+ 0x1c0: 0x15e07, // bgsound
+ 0x1c1: 0x3206, // keygen
+ 0x1c2: 0x2705, // tbody
+ 0x1c5: 0x64406, // onshow
+ 0x1c7: 0x2501, // s
+ 0x1c8: 0x6607, // pattern
+ 0x1cc: 0x14d10, // oncanplaythrough
+ 0x1ce: 0x2d702, // dd
+ 0x1cf: 0x6f906, // srcset
+ 0x1d0: 0x17003, // big
+ 0x1d2: 0x65108, // sortable
+ 0x1d3: 0x48007, // onkeyup
+ 0x1d5: 0x5a406, // onplay
+ 0x1d7: 0x4b804, // meta
+ 0x1d8: 0x40306, // ondrop
+ 0x1da: 0x60008, // onscroll
+ 0x1db: 0x1fb0b, // crossorigin
+ 0x1dc: 0x5730a, // onpageshow
+ 0x1dd: 0x4, // abbr
+ 0x1de: 0x9202, // td
+ 0x1df: 0x58b0f, // contenteditable
+ 0x1e0: 0x27206, // action
+ 0x1e1: 0x1400b, // playsinline
+ 0x1e2: 0x43107, // onfocus
+ 0x1e3: 0x2e008, // hreflang
+ 0x1e5: 0x5160a, // onmouseout
+ 0x1e6: 0x5ea07, // onreset
+ 0x1e7: 0x13c08, // autoplay
+ 0x1e8: 0x63109, // onseeking
+ 0x1ea: 0x67506, // scoped
+ 0x1ec: 0x30a, // radiogroup
+ 0x1ee: 0x3800b, // contextmenu
+ 0x1ef: 0x52e09, // onmouseup
+ 0x1f1: 0x2ca06, // hgroup
+ 0x1f2: 0x2080f, // allowfullscreen
+ 0x1f3: 0x4be08, // tabindex
+ 0x1f6: 0x30f07, // isindex
+ 0x1f7: 0x1a0e, // accept-charset
+ 0x1f8: 0x2ae0e, // formnovalidate
+ 0x1fb: 0x1c90e, // annotation-xml
+ 0x1fc: 0x6e05, // embed
+ 0x1fd: 0x21806, // script
+ 0x1fe: 0xbb06, // dialog
+ 0x1ff: 0x1d707, // command
+}
+
+const atomText = "abbradiogrouparamainavalueaccept-charsetbodyaccesskeygenobrb" +
+ "asefontimeupdateviacacheightmlabelooptgroupatternoembedetail" +
+ "sampictureversedfnoframesetdirnameterowspanomoduleacronymali" +
+ "gnmarkbdialogallowpaymentrequestrikeytypeallowusermediagroup" +
+ "ingaltfooterubyasyncanvasidefaultitleaudioncancelautofocusan" +
+ "dboxmplaceholderautoplaysinlinebdoncanplaythrough1bgsoundisa" +
+ "bledivarbigblinkindraggablegendblockquotebuttonabortcitempro" +
+ "penoncecolgrouplaintextrackcolorcolspannotation-xmlcommandco" +
+ "ntrolshapecoordslotranslatecrossoriginsmallowfullscreenoscri" +
+ "ptfacenterfieldsetfigcaptionafterprintegrityfigurequiredfore" +
+ "ignObjectforeignobjectformactionautocompleteerrorformenctype" +
+ "mustmatchallengeformmethodformnovalidatetimeformtargethgroup" +
+ "osterhiddenhigh2hreflanghttp-equivideonclickiframeimageimgly" +
+ "ph3isindexismappletitemtypemarqueematheadersortedmaxlength4m" +
+ "inlength5mtextareadonlymultiplemutedoncloseamlessourceoncont" +
+ "extmenuitemidoncopyoncuechangeoncutondblclickondragendondrag" +
+ "enterondragexitemreferrerpolicyondragleaveondragoverondragst" +
+ "articleondropzonemptiedondurationchangeonendedonerroronfocus" +
+ "paceronhashchangeoninputmodeloninvalidonkeydownloadonkeypres" +
+ "spellcheckedonkeyupreloadonlanguagechangeonloadeddatalisting" +
+ "onloadedmetadatabindexonloadendonloadstartonmessageerroronmo" +
+ "usedownonmouseenteronmouseleaveonmousemoveonmouseoutputonmou" +
+ "seoveronmouseupromptonmousewheelonofflineononlineonpagehides" +
+ "classectionbluronpageshowbronpastepublicontenteditableonpaus" +
+ "emaponplayingonpopstateonprogressrcdocodeferonratechangeonre" +
+ "jectionhandledonresetonresizesrclangonscrollonsecuritypolicy" +
+ "violationauxclickonseekedonseekingonselectedonshowidth6onsor" +
+ "tableonstalledonstorageonsubmitemscopedonsuspendontoggleonun" +
+ "handledrejectionbeforeprintonunloadonvolumechangeonwaitingon" +
+ "wheeloptimumanifestrongoptionbeforeunloaddressrcsetstylesumm" +
+ "arysupsvgsystemplateworkertypewrap"
diff --git a/vendor/golang.org/x/net/html/atom/table_test.go b/vendor/golang.org/x/net/html/atom/table_test.go
new file mode 100644
index 0000000..8a30762
--- /dev/null
+++ b/vendor/golang.org/x/net/html/atom/table_test.go
@@ -0,0 +1,376 @@
+// Code generated by go generate gen.go; DO NOT EDIT.
+
+//go:generate go run gen.go -test
+
+package atom
+
+var testAtomList = []string{
+ "a",
+ "abbr",
+ "accept",
+ "accept-charset",
+ "accesskey",
+ "acronym",
+ "action",
+ "address",
+ "align",
+ "allowfullscreen",
+ "allowpaymentrequest",
+ "allowusermedia",
+ "alt",
+ "annotation",
+ "annotation-xml",
+ "applet",
+ "area",
+ "article",
+ "as",
+ "aside",
+ "async",
+ "audio",
+ "autocomplete",
+ "autofocus",
+ "autoplay",
+ "b",
+ "base",
+ "basefont",
+ "bdi",
+ "bdo",
+ "bgsound",
+ "big",
+ "blink",
+ "blockquote",
+ "body",
+ "br",
+ "button",
+ "canvas",
+ "caption",
+ "center",
+ "challenge",
+ "charset",
+ "checked",
+ "cite",
+ "class",
+ "code",
+ "col",
+ "colgroup",
+ "color",
+ "cols",
+ "colspan",
+ "command",
+ "content",
+ "contenteditable",
+ "contextmenu",
+ "controls",
+ "coords",
+ "crossorigin",
+ "data",
+ "datalist",
+ "datetime",
+ "dd",
+ "default",
+ "defer",
+ "del",
+ "desc",
+ "details",
+ "dfn",
+ "dialog",
+ "dir",
+ "dirname",
+ "disabled",
+ "div",
+ "dl",
+ "download",
+ "draggable",
+ "dropzone",
+ "dt",
+ "em",
+ "embed",
+ "enctype",
+ "face",
+ "fieldset",
+ "figcaption",
+ "figure",
+ "font",
+ "footer",
+ "for",
+ "foreignObject",
+ "foreignobject",
+ "form",
+ "formaction",
+ "formenctype",
+ "formmethod",
+ "formnovalidate",
+ "formtarget",
+ "frame",
+ "frameset",
+ "h1",
+ "h2",
+ "h3",
+ "h4",
+ "h5",
+ "h6",
+ "head",
+ "header",
+ "headers",
+ "height",
+ "hgroup",
+ "hidden",
+ "high",
+ "hr",
+ "href",
+ "hreflang",
+ "html",
+ "http-equiv",
+ "i",
+ "icon",
+ "id",
+ "iframe",
+ "image",
+ "img",
+ "input",
+ "inputmode",
+ "ins",
+ "integrity",
+ "is",
+ "isindex",
+ "ismap",
+ "itemid",
+ "itemprop",
+ "itemref",
+ "itemscope",
+ "itemtype",
+ "kbd",
+ "keygen",
+ "keytype",
+ "kind",
+ "label",
+ "lang",
+ "legend",
+ "li",
+ "link",
+ "list",
+ "listing",
+ "loop",
+ "low",
+ "main",
+ "malignmark",
+ "manifest",
+ "map",
+ "mark",
+ "marquee",
+ "math",
+ "max",
+ "maxlength",
+ "media",
+ "mediagroup",
+ "menu",
+ "menuitem",
+ "meta",
+ "meter",
+ "method",
+ "mglyph",
+ "mi",
+ "min",
+ "minlength",
+ "mn",
+ "mo",
+ "ms",
+ "mtext",
+ "multiple",
+ "muted",
+ "name",
+ "nav",
+ "nobr",
+ "noembed",
+ "noframes",
+ "nomodule",
+ "nonce",
+ "noscript",
+ "novalidate",
+ "object",
+ "ol",
+ "onabort",
+ "onafterprint",
+ "onautocomplete",
+ "onautocompleteerror",
+ "onauxclick",
+ "onbeforeprint",
+ "onbeforeunload",
+ "onblur",
+ "oncancel",
+ "oncanplay",
+ "oncanplaythrough",
+ "onchange",
+ "onclick",
+ "onclose",
+ "oncontextmenu",
+ "oncopy",
+ "oncuechange",
+ "oncut",
+ "ondblclick",
+ "ondrag",
+ "ondragend",
+ "ondragenter",
+ "ondragexit",
+ "ondragleave",
+ "ondragover",
+ "ondragstart",
+ "ondrop",
+ "ondurationchange",
+ "onemptied",
+ "onended",
+ "onerror",
+ "onfocus",
+ "onhashchange",
+ "oninput",
+ "oninvalid",
+ "onkeydown",
+ "onkeypress",
+ "onkeyup",
+ "onlanguagechange",
+ "onload",
+ "onloadeddata",
+ "onloadedmetadata",
+ "onloadend",
+ "onloadstart",
+ "onmessage",
+ "onmessageerror",
+ "onmousedown",
+ "onmouseenter",
+ "onmouseleave",
+ "onmousemove",
+ "onmouseout",
+ "onmouseover",
+ "onmouseup",
+ "onmousewheel",
+ "onoffline",
+ "ononline",
+ "onpagehide",
+ "onpageshow",
+ "onpaste",
+ "onpause",
+ "onplay",
+ "onplaying",
+ "onpopstate",
+ "onprogress",
+ "onratechange",
+ "onrejectionhandled",
+ "onreset",
+ "onresize",
+ "onscroll",
+ "onsecuritypolicyviolation",
+ "onseeked",
+ "onseeking",
+ "onselect",
+ "onshow",
+ "onsort",
+ "onstalled",
+ "onstorage",
+ "onsubmit",
+ "onsuspend",
+ "ontimeupdate",
+ "ontoggle",
+ "onunhandledrejection",
+ "onunload",
+ "onvolumechange",
+ "onwaiting",
+ "onwheel",
+ "open",
+ "optgroup",
+ "optimum",
+ "option",
+ "output",
+ "p",
+ "param",
+ "pattern",
+ "picture",
+ "ping",
+ "placeholder",
+ "plaintext",
+ "playsinline",
+ "poster",
+ "pre",
+ "preload",
+ "progress",
+ "prompt",
+ "public",
+ "q",
+ "radiogroup",
+ "rb",
+ "readonly",
+ "referrerpolicy",
+ "rel",
+ "required",
+ "reversed",
+ "rows",
+ "rowspan",
+ "rp",
+ "rt",
+ "rtc",
+ "ruby",
+ "s",
+ "samp",
+ "sandbox",
+ "scope",
+ "scoped",
+ "script",
+ "seamless",
+ "section",
+ "select",
+ "selected",
+ "shape",
+ "size",
+ "sizes",
+ "slot",
+ "small",
+ "sortable",
+ "sorted",
+ "source",
+ "spacer",
+ "span",
+ "spellcheck",
+ "src",
+ "srcdoc",
+ "srclang",
+ "srcset",
+ "start",
+ "step",
+ "strike",
+ "strong",
+ "style",
+ "sub",
+ "summary",
+ "sup",
+ "svg",
+ "system",
+ "tabindex",
+ "table",
+ "target",
+ "tbody",
+ "td",
+ "template",
+ "textarea",
+ "tfoot",
+ "th",
+ "thead",
+ "time",
+ "title",
+ "tr",
+ "track",
+ "translate",
+ "tt",
+ "type",
+ "typemustmatch",
+ "u",
+ "ul",
+ "updateviacache",
+ "usemap",
+ "value",
+ "var",
+ "video",
+ "wbr",
+ "width",
+ "workertype",
+ "wrap",
+ "xmp",
+}
diff --git a/vendor/golang.org/x/net/html/charset/charset.go b/vendor/golang.org/x/net/html/charset/charset.go
new file mode 100644
index 0000000..13bed15
--- /dev/null
+++ b/vendor/golang.org/x/net/html/charset/charset.go
@@ -0,0 +1,257 @@
+// Copyright 2013 The Go 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 charset provides common text encodings for HTML documents.
+//
+// The mapping from encoding labels to encodings is defined at
+// https://encoding.spec.whatwg.org/.
+package charset // import "golang.org/x/net/html/charset"
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "mime"
+ "strings"
+ "unicode/utf8"
+
+ "golang.org/x/net/html"
+ "golang.org/x/text/encoding"
+ "golang.org/x/text/encoding/charmap"
+ "golang.org/x/text/encoding/htmlindex"
+ "golang.org/x/text/transform"
+)
+
+// Lookup returns the encoding with the specified label, and its canonical
+// name. It returns nil and the empty string if label is not one of the
+// standard encodings for HTML. Matching is case-insensitive and ignores
+// leading and trailing whitespace. Encoders will use HTML escape sequences for
+// runes that are not supported by the character set.
+func Lookup(label string) (e encoding.Encoding, name string) {
+ e, err := htmlindex.Get(label)
+ if err != nil {
+ return nil, ""
+ }
+ name, _ = htmlindex.Name(e)
+ return &htmlEncoding{e}, name
+}
+
+type htmlEncoding struct{ encoding.Encoding }
+
+func (h *htmlEncoding) NewEncoder() *encoding.Encoder {
+ // HTML requires a non-terminating legacy encoder. We use HTML escapes to
+ // substitute unsupported code points.
+ return encoding.HTMLEscapeUnsupported(h.Encoding.NewEncoder())
+}
+
+// DetermineEncoding determines the encoding of an HTML document by examining
+// up to the first 1024 bytes of content and the declared Content-Type.
+//
+// See http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#determining-the-character-encoding
+func DetermineEncoding(content []byte, contentType string) (e encoding.Encoding, name string, certain bool) {
+ if len(content) > 1024 {
+ content = content[:1024]
+ }
+
+ for _, b := range boms {
+ if bytes.HasPrefix(content, b.bom) {
+ e, name = Lookup(b.enc)
+ return e, name, true
+ }
+ }
+
+ if _, params, err := mime.ParseMediaType(contentType); err == nil {
+ if cs, ok := params["charset"]; ok {
+ if e, name = Lookup(cs); e != nil {
+ return e, name, true
+ }
+ }
+ }
+
+ if len(content) > 0 {
+ e, name = prescan(content)
+ if e != nil {
+ return e, name, false
+ }
+ }
+
+ // Try to detect UTF-8.
+ // First eliminate any partial rune at the end.
+ for i := len(content) - 1; i >= 0 && i > len(content)-4; i-- {
+ b := content[i]
+ if b < 0x80 {
+ break
+ }
+ if utf8.RuneStart(b) {
+ content = content[:i]
+ break
+ }
+ }
+ hasHighBit := false
+ for _, c := range content {
+ if c >= 0x80 {
+ hasHighBit = true
+ break
+ }
+ }
+ if hasHighBit && utf8.Valid(content) {
+ return encoding.Nop, "utf-8", false
+ }
+
+ // TODO: change default depending on user's locale?
+ return charmap.Windows1252, "windows-1252", false
+}
+
+// NewReader returns an io.Reader that converts the content of r to UTF-8.
+// It calls DetermineEncoding to find out what r's encoding is.
+func NewReader(r io.Reader, contentType string) (io.Reader, error) {
+ preview := make([]byte, 1024)
+ n, err := io.ReadFull(r, preview)
+ switch {
+ case err == io.ErrUnexpectedEOF:
+ preview = preview[:n]
+ r = bytes.NewReader(preview)
+ case err != nil:
+ return nil, err
+ default:
+ r = io.MultiReader(bytes.NewReader(preview), r)
+ }
+
+ if e, _, _ := DetermineEncoding(preview, contentType); e != encoding.Nop {
+ r = transform.NewReader(r, e.NewDecoder())
+ }
+ return r, nil
+}
+
+// NewReaderLabel returns a reader that converts from the specified charset to
+// UTF-8. It uses Lookup to find the encoding that corresponds to label, and
+// returns an error if Lookup returns nil. It is suitable for use as
+// encoding/xml.Decoder's CharsetReader function.
+func NewReaderLabel(label string, input io.Reader) (io.Reader, error) {
+ e, _ := Lookup(label)
+ if e == nil {
+ return nil, fmt.Errorf("unsupported charset: %q", label)
+ }
+ return transform.NewReader(input, e.NewDecoder()), nil
+}
+
+func prescan(content []byte) (e encoding.Encoding, name string) {
+ z := html.NewTokenizer(bytes.NewReader(content))
+ for {
+ switch z.Next() {
+ case html.ErrorToken:
+ return nil, ""
+
+ case html.StartTagToken, html.SelfClosingTagToken:
+ tagName, hasAttr := z.TagName()
+ if !bytes.Equal(tagName, []byte("meta")) {
+ continue
+ }
+ attrList := make(map[string]bool)
+ gotPragma := false
+
+ const (
+ dontKnow = iota
+ doNeedPragma
+ doNotNeedPragma
+ )
+ needPragma := dontKnow
+
+ name = ""
+ e = nil
+ for hasAttr {
+ var key, val []byte
+ key, val, hasAttr = z.TagAttr()
+ ks := string(key)
+ if attrList[ks] {
+ continue
+ }
+ attrList[ks] = true
+ for i, c := range val {
+ if 'A' <= c && c <= 'Z' {
+ val[i] = c + 0x20
+ }
+ }
+
+ switch ks {
+ case "http-equiv":
+ if bytes.Equal(val, []byte("content-type")) {
+ gotPragma = true
+ }
+
+ case "content":
+ if e == nil {
+ name = fromMetaElement(string(val))
+ if name != "" {
+ e, name = Lookup(name)
+ if e != nil {
+ needPragma = doNeedPragma
+ }
+ }
+ }
+
+ case "charset":
+ e, name = Lookup(string(val))
+ needPragma = doNotNeedPragma
+ }
+ }
+
+ if needPragma == dontKnow || needPragma == doNeedPragma && !gotPragma {
+ continue
+ }
+
+ if strings.HasPrefix(name, "utf-16") {
+ name = "utf-8"
+ e = encoding.Nop
+ }
+
+ if e != nil {
+ return e, name
+ }
+ }
+ }
+}
+
+func fromMetaElement(s string) string {
+ for s != "" {
+ csLoc := strings.Index(s, "charset")
+ if csLoc == -1 {
+ return ""
+ }
+ s = s[csLoc+len("charset"):]
+ s = strings.TrimLeft(s, " \t\n\f\r")
+ if !strings.HasPrefix(s, "=") {
+ continue
+ }
+ s = s[1:]
+ s = strings.TrimLeft(s, " \t\n\f\r")
+ if s == "" {
+ return ""
+ }
+ if q := s[0]; q == '"' || q == '\'' {
+ s = s[1:]
+ closeQuote := strings.IndexRune(s, rune(q))
+ if closeQuote == -1 {
+ return ""
+ }
+ return s[:closeQuote]
+ }
+
+ end := strings.IndexAny(s, "; \t\n\f\r")
+ if end == -1 {
+ end = len(s)
+ }
+ return s[:end]
+ }
+ return ""
+}
+
+var boms = []struct {
+ bom []byte
+ enc string
+}{
+ {[]byte{0xfe, 0xff}, "utf-16be"},
+ {[]byte{0xff, 0xfe}, "utf-16le"},
+ {[]byte{0xef, 0xbb, 0xbf}, "utf-8"},
+}
diff --git a/vendor/golang.org/x/net/html/charset/charset_test.go b/vendor/golang.org/x/net/html/charset/charset_test.go
new file mode 100644
index 0000000..e4e7d86
--- /dev/null
+++ b/vendor/golang.org/x/net/html/charset/charset_test.go
@@ -0,0 +1,237 @@
+// Copyright 2013 The Go 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 charset
+
+import (
+ "bytes"
+ "encoding/xml"
+ "io/ioutil"
+ "runtime"
+ "strings"
+ "testing"
+
+ "golang.org/x/text/transform"
+)
+
+func transformString(t transform.Transformer, s string) (string, error) {
+ r := transform.NewReader(strings.NewReader(s), t)
+ b, err := ioutil.ReadAll(r)
+ return string(b), err
+}
+
+type testCase struct {
+ utf8, other, otherEncoding string
+}
+
+// testCases for encoding and decoding.
+var testCases = []testCase{
+ {"Résumé", "Résumé", "utf8"},
+ {"Résumé", "R\xe9sum\xe9", "latin1"},
+ {"これは漢字です。", "S0\x8c0o0\"oW[g0Y0\x020", "UTF-16LE"},
+ {"これは漢字です。", "0S0\x8c0oo\"[W0g0Y0\x02", "UTF-16BE"},
+ {"Hello, world", "Hello, world", "ASCII"},
+ {"Gdańsk", "Gda\xf1sk", "ISO-8859-2"},
+ {"Ââ Čč Đđ Ŋŋ Õõ Šš Žž Åå Ää", "\xc2\xe2 \xc8\xe8 \xa9\xb9 \xaf\xbf \xd5\xf5 \xaa\xba \xac\xbc \xc5\xe5 \xc4\xe4", "ISO-8859-10"},
+ {"สำหรับ", "\xca\xd3\xcb\xc3\u047a", "ISO-8859-11"},
+ {"latviešu", "latvie\xf0u", "ISO-8859-13"},
+ {"Seònaid", "Se\xf2naid", "ISO-8859-14"},
+ {"€1 is cheap", "\xa41 is cheap", "ISO-8859-15"},
+ {"românește", "rom\xe2ne\xbate", "ISO-8859-16"},
+ {"nutraĵo", "nutra\xbco", "ISO-8859-3"},
+ {"Kalâdlit", "Kal\xe2dlit", "ISO-8859-4"},
+ {"русский", "\xe0\xe3\xe1\xe1\xda\xd8\xd9", "ISO-8859-5"},
+ {"ελληνικά", "\xe5\xeb\xeb\xe7\xed\xe9\xea\xdc", "ISO-8859-7"},
+ {"Kağan", "Ka\xf0an", "ISO-8859-9"},
+ {"Résumé", "R\x8esum\x8e", "macintosh"},
+ {"Gdańsk", "Gda\xf1sk", "windows-1250"},
+ {"русский", "\xf0\xf3\xf1\xf1\xea\xe8\xe9", "windows-1251"},
+ {"Résumé", "R\xe9sum\xe9", "windows-1252"},
+ {"ελληνικά", "\xe5\xeb\xeb\xe7\xed\xe9\xea\xdc", "windows-1253"},
+ {"Kağan", "Ka\xf0an", "windows-1254"},
+ {"עִבְרִית", "\xf2\xc4\xe1\xc0\xf8\xc4\xe9\xfa", "windows-1255"},
+ {"العربية", "\xc7\xe1\xda\xd1\xc8\xed\xc9", "windows-1256"},
+ {"latviešu", "latvie\xf0u", "windows-1257"},
+ {"Việt", "Vi\xea\xf2t", "windows-1258"},
+ {"สำหรับ", "\xca\xd3\xcb\xc3\u047a", "windows-874"},
+ {"русский", "\xd2\xd5\xd3\xd3\xcb\xc9\xca", "KOI8-R"},
+ {"українська", "\xd5\xcb\xd2\xc1\xa7\xce\xd3\xd8\xcb\xc1", "KOI8-U"},
+ {"Hello 常用國字標準字體表", "Hello \xb1`\xa5\u03b0\xea\xa6r\xbc\u0437\u01e6r\xc5\xe9\xaa\xed", "big5"},
+ {"Hello 常用國字標準字體表", "Hello \xb3\xa3\xd3\xc3\x87\xf8\xd7\xd6\x98\xcb\x9c\xca\xd7\xd6\xf3\x77\xb1\xed", "gbk"},
+ {"Hello 常用國字標準字體表", "Hello \xb3\xa3\xd3\xc3\x87\xf8\xd7\xd6\x98\xcb\x9c\xca\xd7\xd6\xf3\x77\xb1\xed", "gb18030"},
+ {"עִבְרִית", "\x81\x30\xfb\x30\x81\x30\xf6\x34\x81\x30\xf9\x33\x81\x30\xf6\x30\x81\x30\xfb\x36\x81\x30\xf6\x34\x81\x30\xfa\x31\x81\x30\xfb\x38", "gb18030"},
+ {"㧯", "\x82\x31\x89\x38", "gb18030"},
+ {"これは漢字です。", "\x82\xb1\x82\xea\x82\xcd\x8a\xbf\x8e\x9a\x82\xc5\x82\xb7\x81B", "SJIS"},
+ {"Hello, 世界!", "Hello, \x90\xa2\x8aE!", "SJIS"},
+ {"イウエオカ", "\xb2\xb3\xb4\xb5\xb6", "SJIS"},
+ {"これは漢字です。", "\xa4\xb3\xa4\xec\xa4\u03f4\xc1\xbb\xfa\xa4\u01e4\xb9\xa1\xa3", "EUC-JP"},
+ {"Hello, 世界!", "Hello, \x1b$B@$3&\x1b(B!", "ISO-2022-JP"},
+ {"다음과 같은 조건을 따라야 합니다: 저작자표시", "\xb4\xd9\xc0\xbd\xb0\xfa \xb0\xb0\xc0\xba \xc1\xb6\xb0\xc7\xc0\xbb \xb5\xfb\xb6\xf3\xbe\xdf \xc7մϴ\xd9: \xc0\xfa\xc0\xdb\xc0\xdaǥ\xbd\xc3", "EUC-KR"},
+}
+
+func TestDecode(t *testing.T) {
+ testCases := append(testCases, []testCase{
+ // Replace multi-byte maximum subpart of ill-formed subsequence with
+ // single replacement character (WhatWG requirement).
+ {"Rés\ufffdumé", "Rés\xe1\x80umé", "utf8"},
+ }...)
+ for _, tc := range testCases {
+ e, _ := Lookup(tc.otherEncoding)
+ if e == nil {
+ t.Errorf("%s: not found", tc.otherEncoding)
+ continue
+ }
+ s, err := transformString(e.NewDecoder(), tc.other)
+ if err != nil {
+ t.Errorf("%s: decode %q: %v", tc.otherEncoding, tc.other, err)
+ continue
+ }
+ if s != tc.utf8 {
+ t.Errorf("%s: got %q, want %q", tc.otherEncoding, s, tc.utf8)
+ }
+ }
+}
+
+func TestEncode(t *testing.T) {
+ testCases := append(testCases, []testCase{
+ // Use Go-style replacement.
+ {"Rés\xe1\x80umé", "Rés\ufffd\ufffdumé", "utf8"},
+ // U+0144 LATIN SMALL LETTER N WITH ACUTE not supported by encoding.
+ {"Gdańsk", "Gdańsk", "ISO-8859-11"},
+ {"\ufffd", "�", "ISO-8859-11"},
+ {"a\xe1\x80b", "a��b", "ISO-8859-11"},
+ }...)
+ for _, tc := range testCases {
+ e, _ := Lookup(tc.otherEncoding)
+ if e == nil {
+ t.Errorf("%s: not found", tc.otherEncoding)
+ continue
+ }
+ s, err := transformString(e.NewEncoder(), tc.utf8)
+ if err != nil {
+ t.Errorf("%s: encode %q: %s", tc.otherEncoding, tc.utf8, err)
+ continue
+ }
+ if s != tc.other {
+ t.Errorf("%s: got %q, want %q", tc.otherEncoding, s, tc.other)
+ }
+ }
+}
+
+var sniffTestCases = []struct {
+ filename, declared, want string
+}{
+ {"HTTP-charset.html", "text/html; charset=iso-8859-15", "iso-8859-15"},
+ {"UTF-16LE-BOM.html", "", "utf-16le"},
+ {"UTF-16BE-BOM.html", "", "utf-16be"},
+ {"meta-content-attribute.html", "text/html", "iso-8859-15"},
+ {"meta-charset-attribute.html", "text/html", "iso-8859-15"},
+ {"No-encoding-declaration.html", "text/html", "utf-8"},
+ {"HTTP-vs-UTF-8-BOM.html", "text/html; charset=iso-8859-15", "utf-8"},
+ {"HTTP-vs-meta-content.html", "text/html; charset=iso-8859-15", "iso-8859-15"},
+ {"HTTP-vs-meta-charset.html", "text/html; charset=iso-8859-15", "iso-8859-15"},
+ {"UTF-8-BOM-vs-meta-content.html", "text/html", "utf-8"},
+ {"UTF-8-BOM-vs-meta-charset.html", "text/html", "utf-8"},
+}
+
+func TestSniff(t *testing.T) {
+ switch runtime.GOOS {
+ case "nacl": // platforms that don't permit direct file system access
+ t.Skipf("not supported on %q", runtime.GOOS)
+ }
+
+ for _, tc := range sniffTestCases {
+ content, err := ioutil.ReadFile("testdata/" + tc.filename)
+ if err != nil {
+ t.Errorf("%s: error reading file: %v", tc.filename, err)
+ continue
+ }
+
+ _, name, _ := DetermineEncoding(content, tc.declared)
+ if name != tc.want {
+ t.Errorf("%s: got %q, want %q", tc.filename, name, tc.want)
+ continue
+ }
+ }
+}
+
+func TestReader(t *testing.T) {
+ switch runtime.GOOS {
+ case "nacl": // platforms that don't permit direct file system access
+ t.Skipf("not supported on %q", runtime.GOOS)
+ }
+
+ for _, tc := range sniffTestCases {
+ content, err := ioutil.ReadFile("testdata/" + tc.filename)
+ if err != nil {
+ t.Errorf("%s: error reading file: %v", tc.filename, err)
+ continue
+ }
+
+ r, err := NewReader(bytes.NewReader(content), tc.declared)
+ if err != nil {
+ t.Errorf("%s: error creating reader: %v", tc.filename, err)
+ continue
+ }
+
+ got, err := ioutil.ReadAll(r)
+ if err != nil {
+ t.Errorf("%s: error reading from charset.NewReader: %v", tc.filename, err)
+ continue
+ }
+
+ e, _ := Lookup(tc.want)
+ want, err := ioutil.ReadAll(transform.NewReader(bytes.NewReader(content), e.NewDecoder()))
+ if err != nil {
+ t.Errorf("%s: error decoding with hard-coded charset name: %v", tc.filename, err)
+ continue
+ }
+
+ if !bytes.Equal(got, want) {
+ t.Errorf("%s: got %q, want %q", tc.filename, got, want)
+ continue
+ }
+ }
+}
+
+var metaTestCases = []struct {
+ meta, want string
+}{
+ {"", ""},
+ {"text/html", ""},
+ {"text/html; charset utf-8", ""},
+ {"text/html; charset=latin-2", "latin-2"},
+ {"text/html; charset; charset = utf-8", "utf-8"},
+ {`charset="big5"`, "big5"},
+ {"charset='shift_jis'", "shift_jis"},
+}
+
+func TestFromMeta(t *testing.T) {
+ for _, tc := range metaTestCases {
+ got := fromMetaElement(tc.meta)
+ if got != tc.want {
+ t.Errorf("%q: got %q, want %q", tc.meta, got, tc.want)
+ }
+ }
+}
+
+func TestXML(t *testing.T) {
+ const s = "r\xe9sum\xe9"
+
+ d := xml.NewDecoder(strings.NewReader(s))
+ d.CharsetReader = NewReaderLabel
+
+ var a struct {
+ Word string
+ }
+ err := d.Decode(&a)
+ if err != nil {
+ t.Fatalf("Decode: %v", err)
+ }
+
+ want := "résumé"
+ if a.Word != want {
+ t.Errorf("got %q, want %q", a.Word, want)
+ }
+}
diff --git a/vendor/golang.org/x/net/html/charset/testdata/HTTP-charset.html b/vendor/golang.org/x/net/html/charset/testdata/HTTP-charset.html
new file mode 100644
index 0000000..9915fa0
--- /dev/null
+++ b/vendor/golang.org/x/net/html/charset/testdata/HTTP-charset.html
@@ -0,0 +1,48 @@
+
+
+
+ HTTP charset
+
+
+
+
+
+
+
+
+
+
+
+
HTTP charset
+
+
+
+
+
+
+
+
+
+
+
+
+
The character encoding of a page can be set using the HTTP header charset declaration.
+
The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.
The only character encoding declaration for this HTML file is in the HTTP header, which sets the encoding to ISO 8859-15.
A character encoding set in the HTTP header has lower precedence than the UTF-8 signature.
+
The HTTP header attempts to set the character encoding to ISO 8859-15. The page starts with a UTF-8 signature.
The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ýäè. This matches the sequence of bytes above when they are interpreted as UTF-8. If the class name matches the selector then the test will pass.
If the test is unsuccessful, the characters  should appear at the top of the page. These represent the bytes that make up the UTF-8 signature when encountered in the ISO 8859-15 encoding.
The HTTP header has a higher precedence than an encoding declaration in a meta charset attribute.
+
The HTTP header attempts to set the character encoding to ISO 8859-15. The page contains an encoding declaration in a meta charset attribute that attempts to set the character encoding to ISO 8859-1.
The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.
The HTTP header has a higher precedence than an encoding declaration in a meta content attribute.
+
The HTTP header attempts to set the character encoding to ISO 8859-15. The page contains an encoding declaration in a meta content attribute that attempts to set the character encoding to ISO 8859-1.
The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.
A page with no encoding information in HTTP, BOM, XML declaration or meta element will be treated as UTF-8.
+
The test on this page contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ýäè. This matches the sequence of bytes above when they are interpreted as UTF-8. If the class name matches the selector then the test will pass.
The test is read from a server that supports HTTP.
+
+
+
+
+
+
+
diff --git a/vendor/golang.org/x/net/html/charset/testdata/README b/vendor/golang.org/x/net/html/charset/testdata/README
new file mode 100644
index 0000000..38ef0f9
--- /dev/null
+++ b/vendor/golang.org/x/net/html/charset/testdata/README
@@ -0,0 +1,9 @@
+These test cases come from
+http://www.w3.org/International/tests/repository/html5/the-input-byte-stream/results-basics
+
+Distributed under both the W3C Test Suite License
+(http://www.w3.org/Consortium/Legal/2008/04-testsuite-license)
+and the W3C 3-clause BSD License
+(http://www.w3.org/Consortium/Legal/2008/03-bsd-license).
+To contribute to a W3C Test Suite, see the policies and contribution
+forms (http://www.w3.org/2004/10/27-testcases).
diff --git a/vendor/golang.org/x/net/html/charset/testdata/UTF-16BE-BOM.html b/vendor/golang.org/x/net/html/charset/testdata/UTF-16BE-BOM.html
new file mode 100644
index 0000000..3abf7a9
Binary files /dev/null and b/vendor/golang.org/x/net/html/charset/testdata/UTF-16BE-BOM.html differ
diff --git a/vendor/golang.org/x/net/html/charset/testdata/UTF-16LE-BOM.html b/vendor/golang.org/x/net/html/charset/testdata/UTF-16LE-BOM.html
new file mode 100644
index 0000000..76254c9
Binary files /dev/null and b/vendor/golang.org/x/net/html/charset/testdata/UTF-16LE-BOM.html differ
diff --git a/vendor/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-charset.html b/vendor/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-charset.html
new file mode 100644
index 0000000..83de433
--- /dev/null
+++ b/vendor/golang.org/x/net/html/charset/testdata/UTF-8-BOM-vs-meta-charset.html
@@ -0,0 +1,49 @@
+
+
+
+ UTF-8 BOM vs meta charset
+
+
+
+
+
+
+
+
+
+
+
+
UTF-8 BOM vs meta charset
+
+
+
+
+
+
+
+
+
+
+
+
+
A page with a UTF-8 BOM will be recognized as UTF-8 even if the meta charset attribute declares a different encoding.
+
The page contains an encoding declaration in a meta charset attribute that attempts to set the character encoding to ISO 8859-15, but the file starts with a UTF-8 signature.
The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ýäè. This matches the sequence of bytes above when they are interpreted as UTF-8. If the class name matches the selector then the test will pass.
A page with a UTF-8 BOM will be recognized as UTF-8 even if the meta content attribute declares a different encoding.
+
The page contains an encoding declaration in a meta content attribute that attempts to set the character encoding to ISO 8859-15, but the file starts with a UTF-8 signature.
The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ýäè. This matches the sequence of bytes above when they are interpreted as UTF-8. If the class name matches the selector then the test will pass.
The character encoding of the page can be set by a meta element with charset attribute.
+
The only character encoding declaration for this HTML file is in the charset attribute of the meta element, which declares the encoding to be ISO 8859-15.
The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.
The character encoding of the page can be set by a meta element with http-equiv and content attributes.
+
The only character encoding declaration for this HTML file is in the content attribute of the meta element, which declares the encoding to be ISO 8859-15.
The test contains a div with a class name that contains the following sequence of bytes: 0xC3 0xBD 0xC3 0xA4 0xC3 0xA8. These represent different sequences of characters in ISO 8859-15, ISO 8859-1 and UTF-8. The external, UTF-8-encoded stylesheet contains a selector .test div.ÜÀÚ. This matches the sequence of bytes above when they are interpreted as ISO 8859-15. If the class name matches the selector then the test will pass.