Browse Source

Merge pull request #549 from fatedier/dev

bump version to v0.14.0
fatedier 7 years ago
parent
commit
3a2946a2ff
100 changed files with 13583 additions and 449 deletions
  1. 1 0
      .travis.yml
  2. 21 0
      Dockerfile_multiple_build
  3. 0 134
      Godeps/Godeps.json
  4. 54 4
      README.md
  5. 59 3
      README_zh.md
  6. 2 2
      client/admin_api.go
  7. 49 48
      client/control.go
  8. 110 6
      client/proxy.go
  9. 2 2
      client/service.go
  10. 318 0
      client/visitor.go
  11. 0 145
      client/vistor.go
  12. 3 3
      cmd/frpc/main.go
  13. 26 8
      conf/frpc_full.ini
  14. 7 1
      conf/frps_full.ini
  15. 75 0
      glide.lock
  16. 73 0
      glide.yaml
  17. 2 0
      models/config/client_common.go
  18. 98 8
      models/config/proxy.go
  19. 19 0
      models/config/server_common.go
  20. 1 0
      models/consts/consts.go
  21. 50 20
      models/msg/msg.go
  22. 4 3
      server/control.go
  23. 16 16
      server/manager.go
  24. 182 0
      server/nathole.go
  25. 55 2
      server/proxy.go
  26. 47 27
      server/service.go
  27. 15 1
      utils/net/kcp.go
  28. 9 15
      utils/version/version.go
  29. 7 1
      utils/vhost/vhost.go
  30. 119 0
      vendor/github.com/armon/go-socks5/auth_test.go
  31. 24 0
      vendor/github.com/armon/go-socks5/credentials_test.go
  32. 169 0
      vendor/github.com/armon/go-socks5/request_test.go
  33. 21 0
      vendor/github.com/armon/go-socks5/resolver_test.go
  34. 24 0
      vendor/github.com/armon/go-socks5/ruleset_test.go
  35. 110 0
      vendor/github.com/armon/go-socks5/socks5_test.go
  36. 22 0
      vendor/github.com/davecgh/go-spew/.gitignore
  37. 14 0
      vendor/github.com/davecgh/go-spew/.travis.yml
  38. 205 0
      vendor/github.com/davecgh/go-spew/README.md
  39. 22 0
      vendor/github.com/davecgh/go-spew/cov_report.sh
  40. 298 0
      vendor/github.com/davecgh/go-spew/spew/common_test.go
  41. 1042 0
      vendor/github.com/davecgh/go-spew/spew/dump_test.go
  42. 99 0
      vendor/github.com/davecgh/go-spew/spew/dumpcgo_test.go
  43. 26 0
      vendor/github.com/davecgh/go-spew/spew/dumpnocgo_test.go
  44. 226 0
      vendor/github.com/davecgh/go-spew/spew/example_test.go
  45. 1558 0
      vendor/github.com/davecgh/go-spew/spew/format_test.go
  46. 87 0
      vendor/github.com/davecgh/go-spew/spew/internal_test.go
  47. 102 0
      vendor/github.com/davecgh/go-spew/spew/internalunsafe_test.go
  48. 320 0
      vendor/github.com/davecgh/go-spew/spew/spew_test.go
  49. 82 0
      vendor/github.com/davecgh/go-spew/spew/testdata/dumpcgo.go
  50. 61 0
      vendor/github.com/davecgh/go-spew/test_coverage.txt
  51. 1536 0
      vendor/github.com/docopt/docopt-go/docopt_test.go
  52. 37 0
      vendor/github.com/docopt/docopt-go/example_test.go
  53. 29 0
      vendor/github.com/docopt/docopt-go/examples/arguments/arguments_example.go
  54. 26 0
      vendor/github.com/docopt/docopt-go/examples/calculator/calculator_example.go
  55. 76 0
      vendor/github.com/docopt/docopt-go/examples/config_file/config_file_example.go
  56. 22 0
      vendor/github.com/docopt/docopt-go/examples/counted/counted_example.go
  57. 38 0
      vendor/github.com/docopt/docopt-go/examples/git/branch/git_branch.go
  58. 30 0
      vendor/github.com/docopt/docopt-go/examples/git/checkout/git_checkout.go
  59. 37 0
      vendor/github.com/docopt/docopt-go/examples/git/clone/git_clone.go
  60. 108 0
      vendor/github.com/docopt/docopt-go/examples/git/git.go
  61. 34 0
      vendor/github.com/docopt/docopt-go/examples/git/push/git_push.go
  62. 28 0
      vendor/github.com/docopt/docopt-go/examples/git/remote/git_remote.go
  63. 28 0
      vendor/github.com/docopt/docopt-go/examples/naval_fate/naval_fate.go
  64. 19 0
      vendor/github.com/docopt/docopt-go/examples/odd_even/odd_even_example.go
  65. 43 0
      vendor/github.com/docopt/docopt-go/examples/options/options_example.go
  66. 24 0
      vendor/github.com/docopt/docopt-go/examples/options_shortcut/options_shortcut_example.go
  67. 16 0
      vendor/github.com/docopt/docopt-go/examples/quick/quick_example.go
  68. 31 0
      vendor/github.com/docopt/docopt-go/examples/type_assert/type_assert_example.go
  69. 17 0
      vendor/github.com/fatedier/beego/.github/ISSUE_TEMPLATE
  70. 6 0
      vendor/github.com/fatedier/beego/.gitignore
  71. 51 0
      vendor/github.com/fatedier/beego/.travis.yml
  72. 52 0
      vendor/github.com/fatedier/beego/CONTRIBUTING.md
  73. 62 0
      vendor/github.com/fatedier/beego/README.md
  74. 401 0
      vendor/github.com/fatedier/beego/admin.go
  75. 73 0
      vendor/github.com/fatedier/beego/admin_test.go
  76. 286 0
      vendor/github.com/fatedier/beego/adminui.go
  77. 366 0
      vendor/github.com/fatedier/beego/app.go
  78. 100 0
      vendor/github.com/fatedier/beego/beego.go
  79. 59 0
      vendor/github.com/fatedier/beego/cache/README.md
  80. 103 0
      vendor/github.com/fatedier/beego/cache/cache.go
  81. 168 0
      vendor/github.com/fatedier/beego/cache/cache_test.go
  82. 100 0
      vendor/github.com/fatedier/beego/cache/conv.go
  83. 143 0
      vendor/github.com/fatedier/beego/cache/conv_test.go
  84. 255 0
      vendor/github.com/fatedier/beego/cache/file.go
  85. 191 0
      vendor/github.com/fatedier/beego/cache/memcache/memcache.go
  86. 108 0
      vendor/github.com/fatedier/beego/cache/memcache/memcache_test.go
  87. 244 0
      vendor/github.com/fatedier/beego/cache/memory.go
  88. 240 0
      vendor/github.com/fatedier/beego/cache/redis/redis.go
  89. 106 0
      vendor/github.com/fatedier/beego/cache/redis/redis_test.go
  90. 240 0
      vendor/github.com/fatedier/beego/cache/ssdb/ssdb.go
  91. 104 0
      vendor/github.com/fatedier/beego/cache/ssdb/ssdb_test.go
  92. 489 0
      vendor/github.com/fatedier/beego/config.go
  93. 242 0
      vendor/github.com/fatedier/beego/config/config.go
  94. 55 0
      vendor/github.com/fatedier/beego/config/config_test.go
  95. 85 0
      vendor/github.com/fatedier/beego/config/env/env.go
  96. 75 0
      vendor/github.com/fatedier/beego/config/env/env_test.go
  97. 134 0
      vendor/github.com/fatedier/beego/config/fake.go
  98. 474 0
      vendor/github.com/fatedier/beego/config/ini.go
  99. 190 0
      vendor/github.com/fatedier/beego/config/ini_test.go
  100. 266 0
      vendor/github.com/fatedier/beego/config/json.go

+ 1 - 0
.travis.yml

@@ -3,6 +3,7 @@ language: go
 
 go:
     - 1.8.x
+    - 1.x
 
 install:
     - make

+ 21 - 0
Dockerfile_multiple_build

@@ -0,0 +1,21 @@
+FROM golang:1.8 as frpBuild
+
+COPY . /go/src/github.com/fatedier/frp
+
+ENV CGO_ENABLED=0
+
+RUN cd /go/src/github.com/fatedier/frp \
+ && make
+
+FROM alpine:3.6
+
+COPY --from=frpBuild /go/src/github.com/fatedier/frp/bin/frpc /
+COPY --from=frpBuild /go/src/github.com/fatedier/frp/conf/frpc.ini /
+COPY --from=frpBuild /go/src/github.com/fatedier/frp/bin/frps /
+COPY --from=frpBuild /go/src/github.com/fatedier/frp/conf/frps.ini /
+
+EXPOSE 80 443 6000 7000 7500
+
+WORKDIR /
+
+CMD ["/frps","-c","frps.ini"]

+ 0 - 134
Godeps/Godeps.json

@@ -1,134 +0,0 @@
-{
-	"ImportPath": "github.com/fatedier/frp",
-	"GoVersion": "go1.8",
-	"GodepVersion": "v79",
-	"Packages": [
-		"./..."
-	],
-	"Deps": [
-		{
-			"ImportPath": "github.com/armon/go-socks5",
-			"Rev": "e75332964ef517daa070d7c38a9466a0d687e0a5"
-		},
-		{
-			"ImportPath": "github.com/davecgh/go-spew/spew",
-			"Comment": "v1.1.0",
-			"Rev": "346938d642f2ec3594ed81d874461961cd0faa76"
-		},
-		{
-			"ImportPath": "github.com/docopt/docopt-go",
-			"Comment": "0.6.2",
-			"Rev": "784ddc588536785e7299f7272f39101f7faccc3f"
-		},
-		{
-			"ImportPath": "github.com/fatedier/beego/logs",
-			"Comment": "v1.7.2-72-gf73c369",
-			"Rev": "f73c3692bbd70a83728cb59b2c0423ff95e4ecea"
-		},
-		{
-			"ImportPath": "github.com/golang/snappy",
-			"Rev": "5979233c5d6225d4a8e438cdd0b411888449ddab"
-		},
-		{
-			"ImportPath": "github.com/julienschmidt/httprouter",
-			"Comment": "v1.1-41-g8a45e95",
-			"Rev": "8a45e95fc75cb77048068a62daed98cc22fdac7c"
-		},
-		{
-			"ImportPath": "github.com/klauspost/cpuid",
-			"Comment": "v1.0",
-			"Rev": "09cded8978dc9e80714c4d85b0322337b0a1e5e0"
-		},
-		{
-			"ImportPath": "github.com/klauspost/reedsolomon",
-			"Comment": "1.3-1-gdde6ad5",
-			"Rev": "dde6ad55c5e5a6379a4e82dcca32ee407346eb6d"
-		},
-		{
-			"ImportPath": "github.com/pkg/errors",
-			"Comment": "v0.8.0-5-gc605e28",
-			"Rev": "c605e284fe17294bda444b34710735b29d1a9d90"
-		},
-		{
-			"ImportPath": "github.com/pmezard/go-difflib/difflib",
-			"Comment": "v1.0.0",
-			"Rev": "792786c7400a136282c1664665ae0a8db921c6c2"
-		},
-		{
-			"ImportPath": "github.com/rakyll/statik/fs",
-			"Comment": "v0.1.0",
-			"Rev": "274df120e9065bdd08eb1120e0375e3dc1ae8465"
-		},
-		{
-			"ImportPath": "github.com/stretchr/testify/assert",
-			"Comment": "v1.1.4-25-g2402e8e",
-			"Rev": "2402e8e7a02fc811447d11f881aa9746cdc57983"
-		},
-		{
-			"ImportPath": "github.com/vaughan0/go-ini",
-			"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
-		},
-		{
-			"ImportPath": "github.com/xtaci/kcp-go",
-			"Comment": "v3.17",
-			"Rev": "df437e2b8ec365a336200f9d9da53441cf72ed47"
-		},
-		{
-			"ImportPath": "github.com/xtaci/smux",
-			"Comment": "v1.0.5-8-g2de5471",
-			"Rev": "2de5471dfcbc029f5fe1392b83fe784127c4943e"
-		},
-		{
-			"ImportPath": "golang.org/x/crypto/blowfish",
-			"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
-		},
-		{
-			"ImportPath": "golang.org/x/crypto/cast5",
-			"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
-		},
-		{
-			"ImportPath": "golang.org/x/crypto/pbkdf2",
-			"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
-		},
-		{
-			"ImportPath": "golang.org/x/crypto/salsa20",
-			"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
-		},
-		{
-			"ImportPath": "golang.org/x/crypto/salsa20/salsa",
-			"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
-		},
-		{
-			"ImportPath": "golang.org/x/crypto/tea",
-			"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
-		},
-		{
-			"ImportPath": "golang.org/x/crypto/twofish",
-			"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
-		},
-		{
-			"ImportPath": "golang.org/x/crypto/xtea",
-			"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
-		},
-		{
-			"ImportPath": "golang.org/x/net/bpf",
-			"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
-		},
-		{
-			"ImportPath": "golang.org/x/net/context",
-			"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
-		},
-		{
-			"ImportPath": "golang.org/x/net/internal/iana",
-			"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
-		},
-		{
-			"ImportPath": "golang.org/x/net/internal/socket",
-			"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
-		},
-		{
-			"ImportPath": "golang.org/x/net/ipv4",
-			"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
-		}
-	]
-}

+ 54 - 4
README.md

@@ -11,6 +11,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
 ## Table of Contents
 
 <!-- vim-markdown-toc GFM -->
+
 * [What can I do with frp?](#what-can-i-do-with-frp)
 * [Status](#status)
 * [Architecture](#architecture)
@@ -20,6 +21,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
     * [Forward DNS query request](#forward-dns-query-request)
     * [Forward unix domain socket](#forward-unix-domain-socket)
     * [Expose your service in security](#expose-your-service-in-security)
+    * [P2P Mode](#p2p-mode)
     * [Connect website through frpc's network](#connect-website-through-frpcs-network)
 * [Features](#features)
     * [Configuration File](#configuration-file)
@@ -184,7 +186,7 @@ However, we can expose a http or https service using frp.
 
 5. Send dns query request by dig:
 
-  `dig @x.x.x.x -p 6000 www.goolge.com`
+  `dig @x.x.x.x -p 6000 www.google.com`
 
 ### Forward unix domain socket
 
@@ -242,9 +244,9 @@ Configure frps same as above.
   server_addr = x.x.x.x
   server_port = 7000
 
-  [secret_ssh_vistor]
+  [secret_ssh_visitor]
   type = stcp
-  role = vistor
+  role = visitor
   server_name = secret_ssh
   sk = abcdefg
   bind_addr = 127.0.0.1
@@ -255,6 +257,54 @@ Configure frps same as above.
 
   `ssh -oPort=6000 test@127.0.0.1`
 
+### P2P Mode
+
+**xtcp** is designed for transmitting a large amount of data directly between two client.
+
+Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work.
+
+1. Configure a udp port for xtcp:
+
+  ```ini
+  bind_udp_port = 7001
+  ```
+
+2. Start frpc, forward ssh port and `remote_port` is useless:
+
+  ```ini
+  # frpc.ini
+  [common]
+  server_addr = x.x.x.x
+  server_port = 7000
+
+  [p2p_ssh]
+  type = xtcp
+  sk = abcdefg
+  local_ip = 127.0.0.1
+  local_port = 22
+  ```
+
+3. Start another frpc in which you want to connect this ssh server:
+
+  ```ini
+  # frpc.ini
+  [common]
+  server_addr = x.x.x.x
+  server_port = 7000
+
+  [p2p_ssh_visitor]
+  type = xtcp
+  role = visitor
+  server_name = p2p_ssh
+  sk = abcdefg
+  bind_addr = 127.0.0.1
+  bind_port = 6000
+  ```
+
+4. Connect to server in LAN by ssh assuming that username is test:
+
+  `ssh -oPort=6000 test@127.0.0.1`
+
 ### Connect website through frpc's network
 
 Configure frps same as above.
@@ -550,7 +600,7 @@ plugin_http_passwd = abc
 * Direct reverse proxy, like haproxy.
 * Load balance to different service in frpc.
 * Frpc can directly be a webserver for static files.
-* P2p communicate by make udp hole to penetrate NAT.
+* P2p communicate by making udp hole to penetrate NAT.
 * kubernetes ingress support.
 
 

+ 59 - 3
README_zh.md

@@ -9,6 +9,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
 ## 目录
 
 <!-- vim-markdown-toc GFM -->
+
 * [frp 的作用](#frp-的作用)
 * [开发状态](#开发状态)
 * [架构](#架构)
@@ -18,6 +19,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
     * [转发 DNS 查询请求](#转发-dns-查询请求)
     * [转发 Unix域套接字](#转发-unix域套接字)
     * [安全地暴露内网服务](#安全地暴露内网服务)
+    * [点对点内网穿透](#点对点内网穿透)
     * [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
 * [功能说明](#功能说明)
     * [配置文件](#配置文件)
@@ -185,7 +187,7 @@ DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿
 
 5. 通过 dig 测试 UDP 包转发是否成功,预期会返回 `www.google.com` 域名的解析结果:
 
-  `dig @x.x.x.x -p 6000 www.goolge.com`
+  `dig @x.x.x.x -p 6000 www.google.com`
 
 ### 转发 Unix域套接字
 
@@ -246,10 +248,10 @@ frps 的部署步骤同上。
   server_addr = x.x.x.x
   server_port = 7000
 
-  [secret_ssh_vistor]
+  [secret_ssh_visitor]
   type = stcp
   # stcp 的访问者
-  role = vistor
+  role = visitor
   # 要访问的 stcp 代理的名字
   server_name = secret_ssh
   sk = abcdefg
@@ -262,6 +264,60 @@ frps 的部署步骤同上。
 
   `ssh -oPort=6000 test@127.0.0.1`
 
+### 点对点内网穿透
+
+frp 提供了一种新的代理类型 **xtcp** 用于应对在希望传输大量数据且流量不经过服务器的场景。
+
+使用方式同 **stcp** 类似,需要在两边都部署上 frpc 用于建立直接的连接。
+
+目前处于开发的初级阶段,并不能穿透所有类型的 NAT 设备,所以穿透成功率较低。穿透失败时可以尝试 **stcp** 的方式。
+
+1. frps 除正常配置外需要额外配置一个 udp 端口用于支持该类型的客户端:
+
+  ```ini
+  bind_udp_port = 7001
+  ```
+
+2. 启动 frpc,转发内网的 ssh 服务,配置如下,不需要指定远程端口:
+
+  ```ini
+  # frpc.ini
+  [common]
+  server_addr = x.x.x.x
+  server_port = 7000
+
+  [p2p_ssh]
+  type = xtcp
+  # 只有 sk 一致的用户才能访问到此服务
+  sk = abcdefg
+  local_ip = 127.0.0.1
+  local_port = 22
+  ```
+
+3. 在要访问这个服务的机器上启动另外一个 frpc,配置如下:
+
+  ```ini
+  # frpc.ini
+  [common]
+  server_addr = x.x.x.x
+  server_port = 7000
+
+  [p2p_ssh_visitor]
+  type = xtcp
+  # xtcp 的访问者
+  role = visitor
+  # 要访问的 xtcp 代理的名字
+  server_name = p2p_ssh
+  sk = abcdefg
+  # 绑定本地端口用于访问 ssh 服务
+  bind_addr = 127.0.0.1
+  bind_port = 6000
+  ```
+
+4. 通过 ssh 访问内网机器,假设用户名为 test:
+
+  `ssh -oPort=6000 test@127.0.0.1`
+
 ### 通过 frpc 所在机器访问外网
 
 frpc 内置了 http proxy 和 socks5 插件,可以使其他机器通过 frpc 的网络访问互联网。

+ 2 - 2
client/admin_api.go

@@ -64,7 +64,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprout
 		return
 	}
 
-	pxyCfgs, vistorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, newCommonCfg.Start)
+	pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, newCommonCfg.Start)
 	if err != nil {
 		res.Code = 3
 		res.Msg = err.Error()
@@ -72,7 +72,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprout
 		return
 	}
 
-	svr.ctl.reloadConf(pxyCfgs, vistorCfgs)
+	svr.ctl.reloadConf(pxyCfgs, visitorCfgs)
 	log.Info("success reload conf")
 	return
 }

+ 49 - 48
client/control.go

@@ -49,11 +49,11 @@ type Control struct {
 	// proxies
 	proxies map[string]Proxy
 
-	// vistor configures
-	vistorCfgs map[string]config.ProxyConf
+	// visitor configures
+	visitorCfgs map[string]config.ProxyConf
 
-	// vistors
-	vistors map[string]Vistor
+	// visitors
+	visitors map[string]Visitor
 
 	// control connection
 	conn frpNet.Conn
@@ -84,7 +84,7 @@ type Control struct {
 	log.Logger
 }
 
-func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) *Control {
+func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) *Control {
 	loginMsg := &msg.Login{
 		Arch:      runtime.GOARCH,
 		Os:        runtime.GOOS,
@@ -93,16 +93,16 @@ func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, vistorCfgs ma
 		Version:   version.Full(),
 	}
 	return &Control{
-		svr:        svr,
-		loginMsg:   loginMsg,
-		pxyCfgs:    pxyCfgs,
-		vistorCfgs: vistorCfgs,
-		proxies:    make(map[string]Proxy),
-		vistors:    make(map[string]Vistor),
-		sendCh:     make(chan msg.Message, 10),
-		readCh:     make(chan msg.Message, 10),
-		closedCh:   make(chan int),
-		Logger:     log.NewPrefixLogger(""),
+		svr:         svr,
+		loginMsg:    loginMsg,
+		pxyCfgs:     pxyCfgs,
+		visitorCfgs: visitorCfgs,
+		proxies:     make(map[string]Proxy),
+		visitors:    make(map[string]Visitor),
+		sendCh:      make(chan msg.Message, 10),
+		readCh:      make(chan msg.Message, 10),
+		closedCh:    make(chan int),
+		Logger:      log.NewPrefixLogger(""),
 	}
 }
 
@@ -137,16 +137,16 @@ func (ctl *Control) Run() (err error) {
 	go ctl.writer()
 	go ctl.reader()
 
-	// start all local vistors
-	for _, cfg := range ctl.vistorCfgs {
-		vistor := NewVistor(ctl, cfg)
-		err = vistor.Run()
+	// start all local visitors
+	for _, cfg := range ctl.visitorCfgs {
+		visitor := NewVisitor(ctl, cfg)
+		err = visitor.Run()
 		if err != nil {
-			vistor.Warn("start error: %v", err)
+			visitor.Warn("start error: %v", err)
 			continue
 		}
-		ctl.vistors[cfg.GetName()] = vistor
-		vistor.Info("start vistor success")
+		ctl.visitors[cfg.GetName()] = visitor
+		visitor.Info("start visitor success")
 	}
 
 	// send NewProxy message for all configured proxies
@@ -271,9 +271,10 @@ func (ctl *Control) login() (err error) {
 	ctl.conn = conn
 	// update runId got from server
 	ctl.setRunId(loginRespMsg.RunId)
+	config.ClientCommonCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
 	ctl.ClearLogPrefix()
 	ctl.AddLogPrefix(loginRespMsg.RunId)
-	ctl.Info("login to server success, get run id [%s]", loginRespMsg.RunId)
+	ctl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
 
 	// login success, so we let closedCh available again
 	ctl.closedCh = make(chan int)
@@ -440,17 +441,17 @@ func (ctl *Control) controler() {
 				}
 			}
 
-			for _, cfg := range ctl.vistorCfgs {
-				if _, exist := ctl.vistors[cfg.GetName()]; !exist {
-					ctl.Info("try to start vistor [%s]", cfg.GetName())
-					vistor := NewVistor(ctl, cfg)
-					err = vistor.Run()
+			for _, cfg := range ctl.visitorCfgs {
+				if _, exist := ctl.visitors[cfg.GetName()]; !exist {
+					ctl.Info("try to start visitor [%s]", cfg.GetName())
+					visitor := NewVisitor(ctl, cfg)
+					err = visitor.Run()
 					if err != nil {
-						vistor.Warn("start error: %v", err)
+						visitor.Warn("start error: %v", err)
 						continue
 					}
-					ctl.vistors[cfg.GetName()] = vistor
-					vistor.Info("start vistor success")
+					ctl.visitors[cfg.GetName()] = visitor
+					visitor.Info("start visitor success")
 				}
 			}
 			ctl.mu.RUnlock()
@@ -548,7 +549,7 @@ func (ctl *Control) getProxyConf(name string) (conf config.ProxyConf, ok bool) {
 	return
 }
 
-func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) {
+func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) {
 	ctl.mu.Lock()
 	defer ctl.mu.Unlock()
 
@@ -587,35 +588,35 @@ func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, vistorCfgs m
 	}
 	ctl.Info("proxy added: %v", addedPxyNames)
 
-	removedVistorName := make([]string, 0)
-	for name, oldVistorCfg := range ctl.vistorCfgs {
+	removedVisitorName := make([]string, 0)
+	for name, oldVisitorCfg := range ctl.visitorCfgs {
 		del := false
-		cfg, ok := vistorCfgs[name]
+		cfg, ok := visitorCfgs[name]
 		if !ok {
 			del = true
 		} else {
-			if !oldVistorCfg.Compare(cfg) {
+			if !oldVisitorCfg.Compare(cfg) {
 				del = true
 			}
 		}
 
 		if del {
-			removedVistorName = append(removedVistorName, name)
-			delete(ctl.vistorCfgs, name)
-			if vistor, ok := ctl.vistors[name]; ok {
-				vistor.Close()
+			removedVisitorName = append(removedVisitorName, name)
+			delete(ctl.visitorCfgs, name)
+			if visitor, ok := ctl.visitors[name]; ok {
+				visitor.Close()
 			}
-			delete(ctl.vistors, name)
+			delete(ctl.visitors, name)
 		}
 	}
-	ctl.Info("vistor removed: %v", removedVistorName)
+	ctl.Info("visitor removed: %v", removedVisitorName)
 
-	addedVistorName := make([]string, 0)
-	for name, vistorCfg := range vistorCfgs {
-		if _, ok := ctl.vistorCfgs[name]; !ok {
-			ctl.vistorCfgs[name] = vistorCfg
-			addedVistorName = append(addedVistorName, name)
+	addedVisitorName := make([]string, 0)
+	for name, visitorCfg := range visitorCfgs {
+		if _, ok := ctl.visitorCfgs[name]; !ok {
+			ctl.visitorCfgs[name] = visitorCfg
+			addedVisitorName = append(addedVisitorName, name)
 		}
 	}
-	ctl.Info("vistor added: %v", addedVistorName)
+	ctl.Info("visitor added: %v", addedVisitorName)
 }

+ 110 - 6
client/proxy.go

@@ -15,6 +15,7 @@
 package client
 
 import (
+	"bytes"
 	"fmt"
 	"io"
 	"net"
@@ -29,6 +30,7 @@ import (
 	frpIo "github.com/fatedier/frp/utils/io"
 	"github.com/fatedier/frp/utils/log"
 	frpNet "github.com/fatedier/frp/utils/net"
+	"github.com/fatedier/frp/utils/pool"
 )
 
 // Proxy defines how to deal with work connections for different proxy type.
@@ -72,6 +74,11 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
 			BaseProxy: baseProxy,
 			cfg:       cfg,
 		}
+	case *config.XtcpProxyConf:
+		pxy = &XtcpProxy{
+			BaseProxy: baseProxy,
+			cfg:       cfg,
+		}
 	}
 	return
 }
@@ -108,7 +115,8 @@ func (pxy *TcpProxy) Close() {
 }
 
 func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
-	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
+	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
+		[]byte(config.ClientCommonCfg.PrivilegeToken))
 }
 
 // HTTP
@@ -136,7 +144,8 @@ func (pxy *HttpProxy) Close() {
 }
 
 func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
-	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
+	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
+		[]byte(config.ClientCommonCfg.PrivilegeToken))
 }
 
 // HTTPS
@@ -164,7 +173,8 @@ func (pxy *HttpsProxy) Close() {
 }
 
 func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
-	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
+	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
+		[]byte(config.ClientCommonCfg.PrivilegeToken))
 }
 
 // STCP
@@ -192,7 +202,101 @@ func (pxy *StcpProxy) Close() {
 }
 
 func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
-	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
+	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
+		[]byte(config.ClientCommonCfg.PrivilegeToken))
+}
+
+// XTCP
+type XtcpProxy struct {
+	BaseProxy
+
+	cfg         *config.XtcpProxyConf
+	proxyPlugin plugin.Plugin
+}
+
+func (pxy *XtcpProxy) Run() (err error) {
+	if pxy.cfg.Plugin != "" {
+		pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+func (pxy *XtcpProxy) Close() {
+	if pxy.proxyPlugin != nil {
+		pxy.proxyPlugin.Close()
+	}
+}
+
+func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
+	defer conn.Close()
+	var natHoleSidMsg msg.NatHoleSid
+	err := msg.ReadMsgInto(conn, &natHoleSidMsg)
+	if err != nil {
+		pxy.Error("xtcp read from workConn error: %v", err)
+		return
+	}
+
+	natHoleClientMsg := &msg.NatHoleClient{
+		ProxyName: pxy.cfg.ProxyName,
+		Sid:       natHoleSidMsg.Sid,
+	}
+	raddr, _ := net.ResolveUDPAddr("udp",
+		fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort))
+	clientConn, err := net.DialUDP("udp", nil, raddr)
+	defer clientConn.Close()
+
+	err = msg.WriteMsg(clientConn, natHoleClientMsg)
+	if err != nil {
+		pxy.Error("send natHoleClientMsg to server error: %v", err)
+		return
+	}
+
+	// Wait for client address at most 5 seconds.
+	var natHoleRespMsg msg.NatHoleResp
+	clientConn.SetReadDeadline(time.Now().Add(5 * time.Second))
+
+	buf := pool.GetBuf(1024)
+	n, err := clientConn.Read(buf)
+	if err != nil {
+		pxy.Error("get natHoleRespMsg error: %v", err)
+		return
+	}
+	err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
+	if err != nil {
+		pxy.Error("get natHoleRespMsg error: %v", err)
+		return
+	}
+	clientConn.SetReadDeadline(time.Time{})
+	clientConn.Close()
+	pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
+
+	// Send sid to visitor udp address.
+	time.Sleep(time.Second)
+	laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
+	daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.VisitorAddr)
+	if err != nil {
+		pxy.Error("resolve visitor udp address error: %v", err)
+		return
+	}
+
+	lConn, err := net.DialUDP("udp", laddr, daddr)
+	if err != nil {
+		pxy.Error("dial visitor udp address error: %v", err)
+		return
+	}
+	lConn.Write([]byte(natHoleRespMsg.Sid))
+
+	kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.VisitorAddr)
+	if err != nil {
+		pxy.Error("create kcp connection from udp connection error: %v", err)
+		return
+	}
+
+	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
+		frpNet.WrapConn(kcpConn), []byte(pxy.cfg.Sk))
 }
 
 // UDP
@@ -302,7 +406,7 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
 
 // Common handler for tcp work connections.
 func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
-	baseInfo *config.BaseProxyConf, workConn frpNet.Conn) {
+	baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte) {
 
 	var (
 		remote io.ReadWriteCloser
@@ -310,7 +414,7 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
 	)
 	remote = workConn
 	if baseInfo.UseEncryption {
-		remote, err = frpIo.WithEncryption(remote, []byte(config.ClientCommonCfg.PrivilegeToken))
+		remote, err = frpIo.WithEncryption(remote, encKey)
 		if err != nil {
 			workConn.Error("create encryption stream error: %v", err)
 			return

+ 2 - 2
client/service.go

@@ -26,11 +26,11 @@ type Service struct {
 	closedCh chan int
 }
 
-func NewService(pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) (svr *Service) {
+func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) (svr *Service) {
 	svr = &Service{
 		closedCh: make(chan int),
 	}
-	ctl := NewControl(svr, pxyCfgs, vistorCfgs)
+	ctl := NewControl(svr, pxyCfgs, visitorCfgs)
 	svr.ctl = ctl
 	return
 }

+ 318 - 0
client/visitor.go

@@ -0,0 +1,318 @@
+// Copyright 2017 fatedier, fatedier@gmail.com
+//
+// 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 client
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"net"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"golang.org/x/net/ipv4"
+
+	"github.com/fatedier/frp/models/config"
+	"github.com/fatedier/frp/models/msg"
+	frpIo "github.com/fatedier/frp/utils/io"
+	"github.com/fatedier/frp/utils/log"
+	frpNet "github.com/fatedier/frp/utils/net"
+	"github.com/fatedier/frp/utils/pool"
+	"github.com/fatedier/frp/utils/util"
+)
+
+// Visitor is used for forward traffics from local port tot remote service.
+type Visitor interface {
+	Run() error
+	Close()
+	log.Logger
+}
+
+func NewVisitor(ctl *Control, pxyConf config.ProxyConf) (visitor Visitor) {
+	baseVisitor := BaseVisitor{
+		ctl:    ctl,
+		Logger: log.NewPrefixLogger(pxyConf.GetName()),
+	}
+	switch cfg := pxyConf.(type) {
+	case *config.StcpProxyConf:
+		visitor = &StcpVisitor{
+			BaseVisitor: baseVisitor,
+			cfg:         cfg,
+		}
+	case *config.XtcpProxyConf:
+		visitor = &XtcpVisitor{
+			BaseVisitor: baseVisitor,
+			cfg:         cfg,
+		}
+	}
+	return
+}
+
+type BaseVisitor struct {
+	ctl    *Control
+	l      frpNet.Listener
+	closed bool
+	mu     sync.RWMutex
+	log.Logger
+}
+
+type StcpVisitor struct {
+	BaseVisitor
+
+	cfg *config.StcpProxyConf
+}
+
+func (sv *StcpVisitor) Run() (err error) {
+	sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, int64(sv.cfg.BindPort))
+	if err != nil {
+		return
+	}
+
+	go sv.worker()
+	return
+}
+
+func (sv *StcpVisitor) Close() {
+	sv.l.Close()
+}
+
+func (sv *StcpVisitor) worker() {
+	for {
+		conn, err := sv.l.Accept()
+		if err != nil {
+			sv.Warn("stcp local listener closed")
+			return
+		}
+
+		go sv.handleConn(conn)
+	}
+}
+
+func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
+	defer userConn.Close()
+
+	sv.Debug("get a new stcp user connection")
+	visitorConn, err := sv.ctl.connectServer()
+	if err != nil {
+		return
+	}
+	defer visitorConn.Close()
+
+	now := time.Now().Unix()
+	newVisitorConnMsg := &msg.NewVisitorConn{
+		ProxyName:      sv.cfg.ServerName,
+		SignKey:        util.GetAuthKey(sv.cfg.Sk, now),
+		Timestamp:      now,
+		UseEncryption:  sv.cfg.UseEncryption,
+		UseCompression: sv.cfg.UseCompression,
+	}
+	err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
+	if err != nil {
+		sv.Warn("send newVisitorConnMsg to server error: %v", err)
+		return
+	}
+
+	var newVisitorConnRespMsg msg.NewVisitorConnResp
+	visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
+	err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
+	if err != nil {
+		sv.Warn("get newVisitorConnRespMsg error: %v", err)
+		return
+	}
+	visitorConn.SetReadDeadline(time.Time{})
+
+	if newVisitorConnRespMsg.Error != "" {
+		sv.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
+		return
+	}
+
+	var remote io.ReadWriteCloser
+	remote = visitorConn
+	if sv.cfg.UseEncryption {
+		remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
+		if err != nil {
+			sv.Error("create encryption stream error: %v", err)
+			return
+		}
+	}
+
+	if sv.cfg.UseCompression {
+		remote = frpIo.WithCompression(remote)
+	}
+
+	frpIo.Join(userConn, remote)
+}
+
+type XtcpVisitor struct {
+	BaseVisitor
+
+	cfg *config.XtcpProxyConf
+}
+
+func (sv *XtcpVisitor) Run() (err error) {
+	sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, int64(sv.cfg.BindPort))
+	if err != nil {
+		return
+	}
+
+	go sv.worker()
+	return
+}
+
+func (sv *XtcpVisitor) Close() {
+	sv.l.Close()
+}
+
+func (sv *XtcpVisitor) worker() {
+	for {
+		conn, err := sv.l.Accept()
+		if err != nil {
+			sv.Warn("stcp local listener closed")
+			return
+		}
+
+		go sv.handleConn(conn)
+	}
+}
+
+func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
+	defer userConn.Close()
+
+	sv.Debug("get a new xtcp user connection")
+	if config.ClientCommonCfg.ServerUdpPort == 0 {
+		sv.Error("xtcp is not supported by server")
+		return
+	}
+
+	raddr, err := net.ResolveUDPAddr("udp",
+		fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort))
+	visitorConn, err := net.DialUDP("udp", nil, raddr)
+	defer visitorConn.Close()
+
+	now := time.Now().Unix()
+	natHoleVisitorMsg := &msg.NatHoleVisitor{
+		ProxyName: sv.cfg.ServerName,
+		SignKey:   util.GetAuthKey(sv.cfg.Sk, now),
+		Timestamp: now,
+	}
+	err = msg.WriteMsg(visitorConn, natHoleVisitorMsg)
+	if err != nil {
+		sv.Warn("send natHoleVisitorMsg to server error: %v", err)
+		return
+	}
+
+	// Wait for client address at most 10 seconds.
+	var natHoleRespMsg msg.NatHoleResp
+	visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
+	buf := pool.GetBuf(1024)
+	n, err := visitorConn.Read(buf)
+	if err != nil {
+		sv.Warn("get natHoleRespMsg error: %v", err)
+		return
+	}
+
+	err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
+	if err != nil {
+		sv.Warn("get natHoleRespMsg error: %v", err)
+		return
+	}
+	visitorConn.SetReadDeadline(time.Time{})
+	pool.PutBuf(buf)
+
+	sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
+
+	// Close visitorConn, so we can use it's local address.
+	visitorConn.Close()
+
+	// Send detect message.
+	array := strings.Split(natHoleRespMsg.ClientAddr, ":")
+	if len(array) <= 1 {
+		sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
+		return
+	}
+	laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
+	/*
+		for i := 1000; i < 65000; i++ {
+			sv.sendDetectMsg(array[0], int64(i), laddr, "a")
+		}
+	*/
+	port, err := strconv.ParseInt(array[1], 10, 64)
+	if err != nil {
+		sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
+		return
+	}
+	sv.sendDetectMsg(array[0], int64(port), laddr, []byte(natHoleRespMsg.Sid))
+	sv.Trace("send all detect msg done")
+
+	// Listen for visitorConn's address and wait for client connection.
+	lConn, _ := net.ListenUDP("udp", laddr)
+	lConn.SetReadDeadline(time.Now().Add(5 * time.Second))
+	sidBuf := pool.GetBuf(1024)
+	n, _, err = lConn.ReadFromUDP(sidBuf)
+	if err != nil {
+		sv.Warn("get sid from client error: %v", err)
+		return
+	}
+	lConn.SetReadDeadline(time.Time{})
+	if string(sidBuf[:n]) != natHoleRespMsg.Sid {
+		sv.Warn("incorrect sid from client")
+		return
+	}
+	sv.Info("nat hole connection make success, sid [%s]", string(sidBuf[:n]))
+	pool.PutBuf(sidBuf)
+
+	var remote io.ReadWriteCloser
+	remote, err = frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.ClientAddr)
+	if err != nil {
+		sv.Error("create kcp connection from udp connection error: %v", err)
+		return
+	}
+
+	if sv.cfg.UseEncryption {
+		remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
+		if err != nil {
+			sv.Error("create encryption stream error: %v", err)
+			return
+		}
+	}
+
+	if sv.cfg.UseCompression {
+		remote = frpIo.WithCompression(remote)
+	}
+
+	frpIo.Join(userConn, remote)
+	sv.Debug("join connections closed")
+}
+
+func (sv *XtcpVisitor) sendDetectMsg(addr string, port int64, laddr *net.UDPAddr, content []byte) (err error) {
+	daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
+	if err != nil {
+		return err
+	}
+
+	tConn, err := net.DialUDP("udp", laddr, daddr)
+	if err != nil {
+		return err
+	}
+
+	uConn := ipv4.NewConn(tConn)
+	uConn.SetTTL(3)
+
+	tConn.Write(content)
+	tConn.Close()
+	return nil
+}

+ 0 - 145
client/vistor.go

@@ -1,145 +0,0 @@
-// Copyright 2017 fatedier, fatedier@gmail.com
-//
-// 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 client
-
-import (
-	"io"
-	"sync"
-	"time"
-
-	"github.com/fatedier/frp/models/config"
-	"github.com/fatedier/frp/models/msg"
-	frpIo "github.com/fatedier/frp/utils/io"
-	"github.com/fatedier/frp/utils/log"
-	frpNet "github.com/fatedier/frp/utils/net"
-	"github.com/fatedier/frp/utils/util"
-)
-
-// Vistor is used for forward traffics from local port tot remote service.
-type Vistor interface {
-	Run() error
-	Close()
-	log.Logger
-}
-
-func NewVistor(ctl *Control, pxyConf config.ProxyConf) (vistor Vistor) {
-	baseVistor := BaseVistor{
-		ctl:    ctl,
-		Logger: log.NewPrefixLogger(pxyConf.GetName()),
-	}
-	switch cfg := pxyConf.(type) {
-	case *config.StcpProxyConf:
-		vistor = &StcpVistor{
-			BaseVistor: baseVistor,
-			cfg:        cfg,
-		}
-	}
-	return
-}
-
-type BaseVistor struct {
-	ctl    *Control
-	l      frpNet.Listener
-	closed bool
-	mu     sync.RWMutex
-	log.Logger
-}
-
-type StcpVistor struct {
-	BaseVistor
-
-	cfg *config.StcpProxyConf
-}
-
-func (sv *StcpVistor) Run() (err error) {
-	sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, int64(sv.cfg.BindPort))
-	if err != nil {
-		return
-	}
-
-	go sv.worker()
-	return
-}
-
-func (sv *StcpVistor) Close() {
-	sv.l.Close()
-}
-
-func (sv *StcpVistor) worker() {
-	for {
-		conn, err := sv.l.Accept()
-		if err != nil {
-			sv.Warn("stcp local listener closed")
-			return
-		}
-
-		go sv.handleConn(conn)
-	}
-}
-
-func (sv *StcpVistor) handleConn(userConn frpNet.Conn) {
-	defer userConn.Close()
-
-	sv.Debug("get a new stcp user connection")
-	vistorConn, err := sv.ctl.connectServer()
-	if err != nil {
-		return
-	}
-	defer vistorConn.Close()
-
-	now := time.Now().Unix()
-	newVistorConnMsg := &msg.NewVistorConn{
-		ProxyName:      sv.cfg.ServerName,
-		SignKey:        util.GetAuthKey(sv.cfg.Sk, now),
-		Timestamp:      now,
-		UseEncryption:  sv.cfg.UseEncryption,
-		UseCompression: sv.cfg.UseCompression,
-	}
-	err = msg.WriteMsg(vistorConn, newVistorConnMsg)
-	if err != nil {
-		sv.Warn("send newVistorConnMsg to server error: %v", err)
-		return
-	}
-
-	var newVistorConnRespMsg msg.NewVistorConnResp
-	vistorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
-	err = msg.ReadMsgInto(vistorConn, &newVistorConnRespMsg)
-	if err != nil {
-		sv.Warn("get newVistorConnRespMsg error: %v", err)
-		return
-	}
-	vistorConn.SetReadDeadline(time.Time{})
-
-	if newVistorConnRespMsg.Error != "" {
-		sv.Warn("start new vistor connection error: %s", newVistorConnRespMsg.Error)
-		return
-	}
-
-	var remote io.ReadWriteCloser
-	remote = vistorConn
-	if sv.cfg.UseEncryption {
-		remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
-		if err != nil {
-			sv.Error("create encryption stream error: %v", err)
-			return
-		}
-	}
-
-	if sv.cfg.UseCompression {
-		remote = frpIo.WithCompression(remote)
-	}
-
-	frpIo.Join(userConn, remote)
-}

+ 3 - 3
cmd/frpc/main.go

@@ -60,7 +60,7 @@ Options:
 
 func main() {
 	var err error
-	confFile := "./frps.ini"
+	confFile := "./frpc.ini"
 	// the configures parsed from file will be replaced by those from command line if exist
 	args, err := docopt.Parse(usage, nil, true, version.Full(), false)
 
@@ -156,7 +156,7 @@ func main() {
 		}
 	}
 
-	pxyCfgs, vistorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
+	pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
 	if err != nil {
 		fmt.Println(err)
 		os.Exit(1)
@@ -165,7 +165,7 @@ func main() {
 	log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
 		config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
 
-	svr := client.NewService(pxyCfgs, vistorCfgs)
+	svr := client.NewService(pxyCfgs, visitorCfgs)
 
 	// Capture the exit signal if we use kcp.
 	if config.ClientCommonCfg.Protocol == "kcp" {

+ 26 - 8
conf/frpc_full.ini

@@ -119,25 +119,43 @@ plugin_http_passwd = abc
 
 [secret_tcp]
 # If the type is secret tcp, remote_port is useless
-# Who want to connect local port should deploy another frpc with stcp proxy and role is vistor
+# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
 type = stcp
-# sk used for authentication for vistors
+# sk used for authentication for visitors
 sk = abcdefg
 local_ip = 127.0.0.1
 local_port = 22
 use_encryption = false
 use_compression = false
 
-# user of frpc should be same in both stcp server and stcp vistor
-[secret_tcp_vistor]
-# frpc role vistor -> frps -> frpc role server
-role = vistor
+# user of frpc should be same in both stcp server and stcp visitor
+[secret_tcp_visitor]
+# frpc role visitor -> frps -> frpc role server
+role = visitor
 type = stcp
-# the server name you want to vistor
+# the server name you want to visitor
 server_name = secret_tcp
 sk = abcdefg
-# connect this address to vistor stcp server
+# connect this address to visitor stcp server
 bind_addr = 127.0.0.1
 bind_port = 9000
 use_encryption = false
 use_compression = false
+
+[p2p_tcp]
+type = xtcp
+sk = abcdefg
+local_ip = 127.0.0.1
+local_port = 22
+use_encryption = false
+use_compression = false
+
+[p2p_tcp_visitor]
+role = visitor
+type = xtcp
+server_name = p2p_tcp
+sk = abcdefg
+bind_addr = 127.0.0.1
+bind_port = 9001
+use_encryption = false
+use_compression = false

+ 7 - 1
conf/frps_full.ini

@@ -5,6 +5,9 @@
 bind_addr = 0.0.0.0
 bind_port = 7000
 
+# udp port to help make udp hole to penetrate nat
+bind_udp_port = 7001
+
 # udp port used for kcp protocol, it can be same with 'bind_port'
 # if not set, kcp is disabled in frps
 kcp_bind_port = 7000
@@ -16,7 +19,10 @@ kcp_bind_port = 7000
 vhost_http_port = 80
 vhost_https_port = 443
 
-# set dashboard_port to view dashboard of frps
+# set dashboard_addr and dashboard_port to view dashboard of frps
+# dashboard_addr's default value is same with bind_addr
+# dashboard is available only if dashboard_port is set
+dashboard_addr = 0.0.0.0
 dashboard_port = 7500
 
 # dashboard user and pwd for basic auth protect, if not set, both default value is admin

+ 75 - 0
glide.lock

@@ -0,0 +1,75 @@
+hash: 03ff8b71f63e9038c0182a4ef2a55aa9349782f4813c331e2d1f02f3dd15b4f8
+updated: 2017-11-01T16:16:18.577622991+08:00
+imports:
+- name: github.com/armon/go-socks5
+  version: e75332964ef517daa070d7c38a9466a0d687e0a5
+- name: github.com/davecgh/go-spew
+  version: 346938d642f2ec3594ed81d874461961cd0faa76
+  subpackages:
+  - spew
+- name: github.com/docopt/docopt-go
+  version: 784ddc588536785e7299f7272f39101f7faccc3f
+- name: github.com/fatedier/beego
+  version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
+  subpackages:
+  - logs
+- name: github.com/fatedier/kcp-go
+  version: cd167d2f15f451b0f33780ce862fca97adc0331e
+- name: github.com/golang/snappy
+  version: 5979233c5d6225d4a8e438cdd0b411888449ddab
+- name: github.com/julienschmidt/httprouter
+  version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
+- name: github.com/klauspost/cpuid
+  version: 09cded8978dc9e80714c4d85b0322337b0a1e5e0
+- name: github.com/klauspost/reedsolomon
+  version: dde6ad55c5e5a6379a4e82dcca32ee407346eb6d
+- name: github.com/pkg/errors
+  version: c605e284fe17294bda444b34710735b29d1a9d90
+- name: github.com/pmezard/go-difflib
+  version: 792786c7400a136282c1664665ae0a8db921c6c2
+  subpackages:
+  - difflib
+- name: github.com/rakyll/statik
+  version: 274df120e9065bdd08eb1120e0375e3dc1ae8465
+  subpackages:
+  - fs
+- name: github.com/stretchr/testify
+  version: 2402e8e7a02fc811447d11f881aa9746cdc57983
+  subpackages:
+  - assert
+- name: github.com/templexxx/cpufeat
+  version: 3794dfbfb04749f896b521032f69383f24c3687e
+- name: github.com/templexxx/reedsolomon
+  version: 7092926d7d05c415fabb892b1464a03f8228ab80
+- name: github.com/templexxx/xor
+  version: 0af8e873c554da75f37f2049cdffda804533d44c
+- name: github.com/tjfoc/gmsm
+  version: 21d76dee237dbbc8dfe1510000b9bf2733635aa1
+  subpackages:
+  - sm4
+- name: github.com/vaughan0/go-ini
+  version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
+- name: github.com/xtaci/kcp-go
+  version: df437e2b8ec365a336200f9d9da53441cf72ed47
+- name: github.com/xtaci/smux
+  version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
+- name: golang.org/x/crypto
+  version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
+  subpackages:
+  - blowfish
+  - cast5
+  - pbkdf2
+  - salsa20
+  - salsa20/salsa
+  - tea
+  - twofish
+  - xtea
+- name: golang.org/x/net
+  version: e4fa1c5465ad6111f206fc92186b8c83d64adbe1
+  subpackages:
+  - bpf
+  - context
+  - internal/iana
+  - internal/socket
+  - ipv4
+testImports: []

+ 73 - 0
glide.yaml

@@ -0,0 +1,73 @@
+package: github.com/fatedier/frp
+import:
+- package: github.com/armon/go-socks5
+  version: e75332964ef517daa070d7c38a9466a0d687e0a5
+- package: github.com/davecgh/go-spew
+  version: v1.1.0
+  subpackages:
+  - spew
+- package: github.com/docopt/docopt-go
+  version: 0.6.2
+- package: github.com/fatedier/beego
+  version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
+  subpackages:
+  - logs
+- package: github.com/fatedier/kcp-go
+  version: cd167d2f15f451b0f33780ce862fca97adc0331e
+- package: github.com/golang/snappy
+  version: 5979233c5d6225d4a8e438cdd0b411888449ddab
+- package: github.com/julienschmidt/httprouter
+  version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
+- package: github.com/klauspost/cpuid
+  version: v1.0
+- package: github.com/klauspost/reedsolomon
+  version: dde6ad55c5e5a6379a4e82dcca32ee407346eb6d
+- package: github.com/pkg/errors
+  version: c605e284fe17294bda444b34710735b29d1a9d90
+- package: github.com/pmezard/go-difflib
+  version: v1.0.0
+  subpackages:
+  - difflib
+- package: github.com/rakyll/statik
+  version: v0.1.0
+  subpackages:
+  - fs
+- package: github.com/stretchr/testify
+  version: 2402e8e7a02fc811447d11f881aa9746cdc57983
+  subpackages:
+  - assert
+- package: github.com/templexxx/cpufeat
+  version: 3794dfbfb04749f896b521032f69383f24c3687e
+- package: github.com/templexxx/reedsolomon
+  version: 7092926d7d05c415fabb892b1464a03f8228ab80
+- package: github.com/templexxx/xor
+  version: 0.1.2
+- package: github.com/tjfoc/gmsm
+  version: 21d76dee237dbbc8dfe1510000b9bf2733635aa1
+  subpackages:
+  - sm4
+- package: github.com/vaughan0/go-ini
+  version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
+- package: github.com/xtaci/kcp-go
+  version: v3.17
+- package: github.com/xtaci/smux
+  version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
+- package: golang.org/x/crypto
+  version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
+  subpackages:
+  - blowfish
+  - cast5
+  - pbkdf2
+  - salsa20
+  - salsa20/salsa
+  - tea
+  - twofish
+  - xtea
+- package: golang.org/x/net
+  version: e4fa1c5465ad6111f206fc92186b8c83d64adbe1
+  subpackages:
+  - bpf
+  - context
+  - internal/iana
+  - internal/socket
+  - ipv4

+ 2 - 0
models/config/client_common.go

@@ -30,6 +30,7 @@ type ClientCommonConf struct {
 	ConfigFile        string
 	ServerAddr        string
 	ServerPort        int64
+	ServerUdpPort     int64 // this is specified by login response message from frps
 	HttpProxy         string
 	LogFile           string
 	LogWay            string
@@ -55,6 +56,7 @@ func GetDeaultClientCommonConf() *ClientCommonConf {
 		ConfigFile:        "./frpc.ini",
 		ServerAddr:        "0.0.0.0",
 		ServerPort:        7000,
+		ServerUdpPort:     0,
 		HttpProxy:         "",
 		LogFile:           "console",
 		LogWay:            "console",

+ 98 - 8
models/config/proxy.go

@@ -36,6 +36,7 @@ func init() {
 	proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
 	proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
 	proxyConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpProxyConf{})
+	proxyConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpProxyConf{})
 }
 
 // NewConfByType creates a empty ProxyConf object by proxyType.
@@ -594,7 +595,7 @@ type StcpProxyConf struct {
 	LocalSvrConf
 	PluginConf
 
-	// used in role vistor
+	// used in role visitor
 	ServerName string `json:"server_name"`
 	BindAddr   string `json:"bind_addr"`
 	BindPort   int    `json:"bind_port"`
@@ -631,7 +632,7 @@ func (cfg *StcpProxyConf) LoadFromFile(name string, section ini.Section) (err er
 	}
 
 	tmpStr := section["role"]
-	if tmpStr == "server" || tmpStr == "vistor" {
+	if tmpStr == "server" || tmpStr == "visitor" {
 		cfg.Role = tmpStr
 	} else {
 		cfg.Role = "server"
@@ -639,7 +640,7 @@ func (cfg *StcpProxyConf) LoadFromFile(name string, section ini.Section) (err er
 
 	cfg.Sk = section["sk"]
 
-	if tmpStr == "vistor" {
+	if tmpStr == "visitor" {
 		prefix := section["prefix"]
 		cfg.ServerName = prefix + section["server_name"]
 		if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
@@ -672,10 +673,99 @@ func (cfg *StcpProxyConf) Check() (err error) {
 	return
 }
 
+// XTCP
+type XtcpProxyConf struct {
+	BaseProxyConf
+
+	Role string `json:"role"`
+	Sk   string `json:"sk"`
+
+	// used in role server
+	LocalSvrConf
+	PluginConf
+
+	// used in role visitor
+	ServerName string `json:"server_name"`
+	BindAddr   string `json:"bind_addr"`
+	BindPort   int    `json:"bind_port"`
+}
+
+func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
+	cmpConf, ok := cmp.(*XtcpProxyConf)
+	if !ok {
+		return false
+	}
+
+	if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
+		!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
+		!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
+		cfg.Role != cmpConf.Role ||
+		cfg.Sk != cmpConf.Sk ||
+		cfg.ServerName != cmpConf.ServerName ||
+		cfg.BindAddr != cmpConf.BindAddr ||
+		cfg.BindPort != cmpConf.BindPort {
+		return false
+	}
+	return true
+}
+
+// Only for role server.
+func (cfg *XtcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
+	cfg.BaseProxyConf.LoadFromMsg(pMsg)
+	cfg.Sk = pMsg.Sk
+}
+
+func (cfg *XtcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
+	if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
+		return
+	}
+
+	tmpStr := section["role"]
+	if tmpStr == "server" || tmpStr == "visitor" {
+		cfg.Role = tmpStr
+	} else {
+		cfg.Role = "server"
+	}
+
+	cfg.Sk = section["sk"]
+
+	if tmpStr == "visitor" {
+		prefix := section["prefix"]
+		cfg.ServerName = prefix + section["server_name"]
+		if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
+			cfg.BindAddr = "127.0.0.1"
+		}
+
+		if tmpStr, ok := section["bind_port"]; ok {
+			if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
+				return fmt.Errorf("Parse conf error: proxy [%s] bind_port error", name)
+			}
+		} else {
+			return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
+		}
+	} else {
+		if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
+			if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
+				return
+			}
+		}
+	}
+	return
+}
+
+func (cfg *XtcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
+	cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
+	pMsg.Sk = cfg.Sk
+}
+
+func (cfg *XtcpProxyConf) Check() (err error) {
+	return
+}
+
 // if len(startProxy) is 0, start all
 // otherwise just start proxies in startProxy map
 func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]struct{}) (
-	proxyConfs map[string]ProxyConf, vistorConfs map[string]ProxyConf, err error) {
+	proxyConfs map[string]ProxyConf, visitorConfs map[string]ProxyConf, err error) {
 
 	if prefix != "" {
 		prefix += "."
@@ -686,7 +776,7 @@ func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]s
 		startAll = false
 	}
 	proxyConfs = make(map[string]ProxyConf)
-	vistorConfs = make(map[string]ProxyConf)
+	visitorConfs = make(map[string]ProxyConf)
 	for name, section := range conf {
 		_, shouldStart := startProxy[name]
 		if name != "common" && (startAll || shouldStart) {
@@ -694,12 +784,12 @@ func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]s
 			section["prefix"] = prefix
 			cfg, err := NewProxyConfFromFile(name, section)
 			if err != nil {
-				return proxyConfs, vistorConfs, err
+				return proxyConfs, visitorConfs, err
 			}
 
 			role := section["role"]
-			if role == "vistor" {
-				vistorConfs[prefix+name] = cfg
+			if role == "visitor" {
+				visitorConfs[prefix+name] = cfg
 			} else {
 				proxyConfs[prefix+name] = cfg
 			}

+ 19 - 0
models/config/server_common.go

@@ -30,6 +30,7 @@ type ServerCommonConf struct {
 	ConfigFile    string
 	BindAddr      string
 	BindPort      int64
+	BindUdpPort   int64
 	KcpBindPort   int64
 	ProxyBindAddr string
 
@@ -38,6 +39,7 @@ type ServerCommonConf struct {
 
 	// if VhostHttpsPort equals 0, don't listen a public port for https protocol
 	VhostHttpsPort int64
+	DashboardAddr  string
 
 	// if DashboardPort equals 0, dashboard is not available
 	DashboardPort  int64
@@ -66,10 +68,12 @@ func GetDefaultServerCommonConf() *ServerCommonConf {
 		ConfigFile:       "./frps.ini",
 		BindAddr:         "0.0.0.0",
 		BindPort:         7000,
+		BindUdpPort:      0,
 		KcpBindPort:      0,
 		ProxyBindAddr:    "0.0.0.0",
 		VhostHttpPort:    0,
 		VhostHttpsPort:   0,
+		DashboardAddr:    "0.0.0.0",
 		DashboardPort:    0,
 		DashboardUser:    "admin",
 		DashboardPwd:     "admin",
@@ -111,6 +115,14 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
 		}
 	}
 
+	tmpStr, ok = conf.Get("common", "bind_udp_port")
+	if ok {
+		v, err = strconv.ParseInt(tmpStr, 10, 64)
+		if err == nil {
+			cfg.BindUdpPort = v
+		}
+	}
+
 	tmpStr, ok = conf.Get("common", "kcp_bind_port")
 	if ok {
 		v, err = strconv.ParseInt(tmpStr, 10, 64)
@@ -148,6 +160,13 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
 		cfg.VhostHttpsPort = 0
 	}
 
+	tmpStr, ok = conf.Get("common", "dashboard_addr")
+	if ok {
+		cfg.DashboardAddr = tmpStr
+	} else {
+		cfg.DashboardAddr = cfg.BindAddr
+	}
+
 	tmpStr, ok = conf.Get("common", "dashboard_port")
 	if ok {
 		cfg.DashboardPort, err = strconv.ParseInt(tmpStr, 10, 64)

+ 1 - 0
models/consts/consts.go

@@ -28,4 +28,5 @@ var (
 	HttpProxy  string = "http"
 	HttpsProxy string = "https"
 	StcpProxy  string = "stcp"
+	XtcpProxy  string = "xtcp"
 )

+ 50 - 20
models/msg/msg.go

@@ -20,19 +20,23 @@ import (
 )
 
 const (
-	TypeLogin             = 'o'
-	TypeLoginResp         = '1'
-	TypeNewProxy          = 'p'
-	TypeNewProxyResp      = '2'
-	TypeCloseProxy        = 'c'
-	TypeNewWorkConn       = 'w'
-	TypeReqWorkConn       = 'r'
-	TypeStartWorkConn     = 's'
-	TypeNewVistorConn     = 'v'
-	TypeNewVistorConnResp = '3'
-	TypePing              = 'h'
-	TypePong              = '4'
-	TypeUdpPacket         = 'u'
+	TypeLogin              = 'o'
+	TypeLoginResp          = '1'
+	TypeNewProxy           = 'p'
+	TypeNewProxyResp       = '2'
+	TypeCloseProxy         = 'c'
+	TypeNewWorkConn        = 'w'
+	TypeReqWorkConn        = 'r'
+	TypeStartWorkConn      = 's'
+	TypeNewVisitorConn     = 'v'
+	TypeNewVisitorConnResp = '3'
+	TypePing               = 'h'
+	TypePong               = '4'
+	TypeUdpPacket          = 'u'
+	TypeNatHoleVisitor     = 'i'
+	TypeNatHoleClient      = 'n'
+	TypeNatHoleResp        = 'm'
+	TypeNatHoleSid         = '5'
 )
 
 var (
@@ -52,11 +56,15 @@ func init() {
 	TypeMap[TypeNewWorkConn] = reflect.TypeOf(NewWorkConn{})
 	TypeMap[TypeReqWorkConn] = reflect.TypeOf(ReqWorkConn{})
 	TypeMap[TypeStartWorkConn] = reflect.TypeOf(StartWorkConn{})
-	TypeMap[TypeNewVistorConn] = reflect.TypeOf(NewVistorConn{})
-	TypeMap[TypeNewVistorConnResp] = reflect.TypeOf(NewVistorConnResp{})
+	TypeMap[TypeNewVisitorConn] = reflect.TypeOf(NewVisitorConn{})
+	TypeMap[TypeNewVisitorConnResp] = reflect.TypeOf(NewVisitorConnResp{})
 	TypeMap[TypePing] = reflect.TypeOf(Ping{})
 	TypeMap[TypePong] = reflect.TypeOf(Pong{})
 	TypeMap[TypeUdpPacket] = reflect.TypeOf(UdpPacket{})
+	TypeMap[TypeNatHoleVisitor] = reflect.TypeOf(NatHoleVisitor{})
+	TypeMap[TypeNatHoleClient] = reflect.TypeOf(NatHoleClient{})
+	TypeMap[TypeNatHoleResp] = reflect.TypeOf(NatHoleResp{})
+	TypeMap[TypeNatHoleSid] = reflect.TypeOf(NatHoleSid{})
 
 	for k, v := range TypeMap {
 		TypeStringMap[v] = k
@@ -82,9 +90,10 @@ type Login struct {
 }
 
 type LoginResp struct {
-	Version string `json:"version"`
-	RunId   string `json:"run_id"`
-	Error   string `json:"error"`
+	Version       string `json:"version"`
+	RunId         string `json:"run_id"`
+	ServerUdpPort int64  `json:"server_udp_port"`
+	Error         string `json:"error"`
 }
 
 // When frpc login success, send this message to frps for running a new proxy.
@@ -129,7 +138,7 @@ type StartWorkConn struct {
 	ProxyName string `json:"proxy_name"`
 }
 
-type NewVistorConn struct {
+type NewVisitorConn struct {
 	ProxyName      string `json:"proxy_name"`
 	SignKey        string `json:"sign_key"`
 	Timestamp      int64  `json:"timestamp"`
@@ -137,7 +146,7 @@ type NewVistorConn struct {
 	UseCompression bool   `json:"use_compression"`
 }
 
-type NewVistorConnResp struct {
+type NewVisitorConnResp struct {
 	ProxyName string `json:"proxy_name"`
 	Error     string `json:"error"`
 }
@@ -153,3 +162,24 @@ type UdpPacket struct {
 	LocalAddr  *net.UDPAddr `json:"l"`
 	RemoteAddr *net.UDPAddr `json:"r"`
 }
+
+type NatHoleVisitor struct {
+	ProxyName string `json:"proxy_name"`
+	SignKey   string `json:"sign_key"`
+	Timestamp int64  `json:"timestamp"`
+}
+
+type NatHoleClient struct {
+	ProxyName string `json:"proxy_name"`
+	Sid       string `json:"sid"`
+}
+
+type NatHoleResp struct {
+	Sid         string `json:"sid"`
+	VisitorAddr string `json:"visitor_addr"`
+	ClientAddr  string `json:"client_addr"`
+}
+
+type NatHoleSid struct {
+	Sid string `json;"sid"`
+}

+ 4 - 3
server/control.go

@@ -97,9 +97,10 @@ func NewControl(svr *Service, ctlConn net.Conn, loginMsg *msg.Login) *Control {
 // Start send a login success message to client and start working.
 func (ctl *Control) Start() {
 	loginRespMsg := &msg.LoginResp{
-		Version: version.Full(),
-		RunId:   ctl.runId,
-		Error:   "",
+		Version:       version.Full(),
+		RunId:         ctl.runId,
+		ServerUdpPort: config.ServerCommonCfg.BindUdpPort,
+		Error:         "",
 	}
 	msg.WriteMsg(ctl.conn, loginRespMsg)
 

+ 16 - 16
server/manager.go

@@ -93,46 +93,46 @@ func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) {
 	return
 }
 
-// Manager for vistor listeners.
-type VistorManager struct {
-	vistorListeners map[string]*frpNet.CustomListener
-	skMap           map[string]string
+// Manager for visitor listeners.
+type VisitorManager struct {
+	visitorListeners map[string]*frpNet.CustomListener
+	skMap            map[string]string
 
 	mu sync.RWMutex
 }
 
-func NewVistorManager() *VistorManager {
-	return &VistorManager{
-		vistorListeners: make(map[string]*frpNet.CustomListener),
-		skMap:           make(map[string]string),
+func NewVisitorManager() *VisitorManager {
+	return &VisitorManager{
+		visitorListeners: make(map[string]*frpNet.CustomListener),
+		skMap:            make(map[string]string),
 	}
 }
 
-func (vm *VistorManager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
+func (vm *VisitorManager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
 	vm.mu.Lock()
 	defer vm.mu.Unlock()
 
-	if _, ok := vm.vistorListeners[name]; ok {
+	if _, ok := vm.visitorListeners[name]; ok {
 		err = fmt.Errorf("custom listener for [%s] is repeated", name)
 		return
 	}
 
 	l = frpNet.NewCustomListener()
-	vm.vistorListeners[name] = l
+	vm.visitorListeners[name] = l
 	vm.skMap[name] = sk
 	return
 }
 
-func (vm *VistorManager) NewConn(name string, conn frpNet.Conn, timestamp int64, signKey string,
+func (vm *VisitorManager) NewConn(name string, conn frpNet.Conn, timestamp int64, signKey string,
 	useEncryption bool, useCompression bool) (err error) {
 
 	vm.mu.RLock()
 	defer vm.mu.RUnlock()
 
-	if l, ok := vm.vistorListeners[name]; ok {
+	if l, ok := vm.visitorListeners[name]; ok {
 		var sk string
 		if sk = vm.skMap[name]; util.GetAuthKey(sk, timestamp) != signKey {
-			err = fmt.Errorf("vistor connection of [%s] auth failed", name)
+			err = fmt.Errorf("visitor connection of [%s] auth failed", name)
 			return
 		}
 
@@ -154,10 +154,10 @@ func (vm *VistorManager) NewConn(name string, conn frpNet.Conn, timestamp int64,
 	return
 }
 
-func (vm *VistorManager) CloseListener(name string) {
+func (vm *VisitorManager) CloseListener(name string) {
 	vm.mu.Lock()
 	defer vm.mu.Unlock()
 
-	delete(vm.vistorListeners, name)
+	delete(vm.visitorListeners, name)
 	delete(vm.skMap, name)
 }

+ 182 - 0
server/nathole.go

@@ -0,0 +1,182 @@
+package server
+
+import (
+	"bytes"
+	"fmt"
+	"net"
+	"sync"
+	"time"
+
+	"github.com/fatedier/frp/models/msg"
+	"github.com/fatedier/frp/utils/errors"
+	"github.com/fatedier/frp/utils/log"
+	"github.com/fatedier/frp/utils/pool"
+	"github.com/fatedier/frp/utils/util"
+)
+
+// Timeout seconds.
+var NatHoleTimeout int64 = 10
+
+type NatHoleController struct {
+	listener *net.UDPConn
+
+	clientCfgs map[string]*NatHoleClientCfg
+	sessions   map[string]*NatHoleSession
+
+	mu sync.RWMutex
+}
+
+func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error) {
+	addr, err := net.ResolveUDPAddr("udp", udpBindAddr)
+	if err != nil {
+		return nil, err
+	}
+	lconn, err := net.ListenUDP("udp", addr)
+	if err != nil {
+		return nil, err
+	}
+	nc = &NatHoleController{
+		listener:   lconn,
+		clientCfgs: make(map[string]*NatHoleClientCfg),
+		sessions:   make(map[string]*NatHoleSession),
+	}
+	return nc, nil
+}
+
+func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan string) {
+	clientCfg := &NatHoleClientCfg{
+		Name:  name,
+		Sk:    sk,
+		SidCh: make(chan string),
+	}
+	nc.mu.Lock()
+	nc.clientCfgs[name] = clientCfg
+	nc.mu.Unlock()
+	return clientCfg.SidCh
+}
+
+func (nc *NatHoleController) CloseClient(name string) {
+	nc.mu.Lock()
+	defer nc.mu.Unlock()
+	delete(nc.clientCfgs, name)
+}
+
+func (nc *NatHoleController) Run() {
+	for {
+		buf := pool.GetBuf(1024)
+		n, raddr, err := nc.listener.ReadFromUDP(buf)
+		if err != nil {
+			log.Trace("nat hole listener read from udp error: %v", err)
+			return
+		}
+
+		rd := bytes.NewReader(buf[:n])
+		rawMsg, err := msg.ReadMsg(rd)
+		if err != nil {
+			log.Trace("read nat hole message error: %v", err)
+			continue
+		}
+
+		switch m := rawMsg.(type) {
+		case *msg.NatHoleVisitor:
+			go nc.HandleVisitor(m, raddr)
+		case *msg.NatHoleClient:
+			go nc.HandleClient(m, raddr)
+		default:
+			log.Trace("error nat hole message type")
+			continue
+		}
+		pool.PutBuf(buf)
+	}
+}
+
+func (nc *NatHoleController) GenSid() string {
+	t := time.Now().Unix()
+	id, _ := util.RandId()
+	return fmt.Sprintf("%d%s", t, id)
+}
+
+func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDPAddr) {
+	sid := nc.GenSid()
+	session := &NatHoleSession{
+		Sid:         sid,
+		VisitorAddr: raddr,
+		NotifyCh:    make(chan struct{}, 0),
+	}
+	nc.mu.Lock()
+	clientCfg, ok := nc.clientCfgs[m.ProxyName]
+	if !ok || m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) {
+		nc.mu.Unlock()
+		return
+	}
+	nc.sessions[sid] = session
+	nc.mu.Unlock()
+	log.Trace("handle visitor message, sid [%s]", sid)
+
+	defer func() {
+		nc.mu.Lock()
+		delete(nc.sessions, sid)
+		nc.mu.Unlock()
+	}()
+
+	err := errors.PanicToError(func() {
+		clientCfg.SidCh <- sid
+	})
+	if err != nil {
+		return
+	}
+
+	// Wait client connections.
+	select {
+	case <-session.NotifyCh:
+		resp := nc.GenNatHoleResponse(raddr, session)
+		log.Trace("send nat hole response to visitor")
+		nc.listener.WriteToUDP(resp, raddr)
+	case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
+		return
+	}
+}
+
+func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAddr) {
+	nc.mu.RLock()
+	session, ok := nc.sessions[m.Sid]
+	nc.mu.RUnlock()
+	if !ok {
+		return
+	}
+	log.Trace("handle client message, sid [%s]", session.Sid)
+	session.ClientAddr = raddr
+	session.NotifyCh <- struct{}{}
+
+	resp := nc.GenNatHoleResponse(raddr, session)
+	log.Trace("send nat hole response to client")
+	nc.listener.WriteToUDP(resp, raddr)
+}
+
+func (nc *NatHoleController) GenNatHoleResponse(raddr *net.UDPAddr, session *NatHoleSession) []byte {
+	m := &msg.NatHoleResp{
+		Sid:         session.Sid,
+		VisitorAddr: session.VisitorAddr.String(),
+		ClientAddr:  session.ClientAddr.String(),
+	}
+	b := bytes.NewBuffer(nil)
+	err := msg.WriteMsg(b, m)
+	if err != nil {
+		return []byte("")
+	}
+	return b.Bytes()
+}
+
+type NatHoleSession struct {
+	Sid         string
+	VisitorAddr *net.UDPAddr
+	ClientAddr  *net.UDPAddr
+
+	NotifyCh chan struct{}
+}
+
+type NatHoleClientCfg struct {
+	Name  string
+	Sk    string
+	SidCh chan string
+}

+ 55 - 2
server/proxy.go

@@ -148,6 +148,11 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
 			BaseProxy: basePxy,
 			cfg:       cfg,
 		}
+	case *config.XtcpProxyConf:
+		pxy = &XtcpProxy{
+			BaseProxy: basePxy,
+			cfg:       cfg,
+		}
 	default:
 		return pxy, fmt.Errorf("proxy type not support")
 	}
@@ -285,7 +290,7 @@ type StcpProxy struct {
 }
 
 func (pxy *StcpProxy) Run() error {
-	listener, err := pxy.ctl.svr.vistorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
+	listener, err := pxy.ctl.svr.visitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
 	if err != nil {
 		return err
 	}
@@ -303,7 +308,55 @@ func (pxy *StcpProxy) GetConf() config.ProxyConf {
 
 func (pxy *StcpProxy) Close() {
 	pxy.BaseProxy.Close()
-	pxy.ctl.svr.vistorManager.CloseListener(pxy.GetName())
+	pxy.ctl.svr.visitorManager.CloseListener(pxy.GetName())
+}
+
+type XtcpProxy struct {
+	BaseProxy
+	cfg *config.XtcpProxyConf
+
+	closeCh chan struct{}
+}
+
+func (pxy *XtcpProxy) Run() error {
+	if pxy.ctl.svr.natHoleController == nil {
+		pxy.Error("udp port for xtcp is not specified.")
+		return fmt.Errorf("xtcp is not supported in frps")
+	}
+	sidCh := pxy.ctl.svr.natHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk)
+	go func() {
+		for {
+			select {
+			case <-pxy.closeCh:
+				break
+			case sid := <-sidCh:
+				workConn, err := pxy.GetWorkConnFromPool()
+				if err != nil {
+					continue
+				}
+				m := &msg.NatHoleSid{
+					Sid: sid,
+				}
+				err = msg.WriteMsg(workConn, m)
+				if err != nil {
+					pxy.Warn("write nat hole sid package error, %v", err)
+				}
+			}
+		}
+	}()
+	return nil
+}
+
+func (pxy *XtcpProxy) GetConf() config.ProxyConf {
+	return pxy.cfg
+}
+
+func (pxy *XtcpProxy) Close() {
+	pxy.BaseProxy.Close()
+	pxy.ctl.svr.natHoleController.CloseClient(pxy.GetName())
+	errors.PanicToError(func() {
+		close(pxy.closeCh)
+	})
 }
 
 type UdpProxy struct {

+ 47 - 27
server/service.go

@@ -56,46 +56,50 @@ type Service struct {
 	// Manage all proxies.
 	pxyManager *ProxyManager
 
-	// Manage all vistor listeners.
-	vistorManager *VistorManager
+	// Manage all visitor listeners.
+	visitorManager *VisitorManager
+
+	// Controller for nat hole connections.
+	natHoleController *NatHoleController
 }
 
 func NewService() (svr *Service, err error) {
 	svr = &Service{
-		ctlManager:    NewControlManager(),
-		pxyManager:    NewProxyManager(),
-		vistorManager: NewVistorManager(),
+		ctlManager:     NewControlManager(),
+		pxyManager:     NewProxyManager(),
+		visitorManager: NewVisitorManager(),
 	}
+	cfg := config.ServerCommonCfg
 
 	// Init assets.
-	err = assets.Load(config.ServerCommonCfg.AssetsDir)
+	err = assets.Load(cfg.AssetsDir)
 	if err != nil {
 		err = fmt.Errorf("Load assets error: %v", err)
 		return
 	}
 
 	// Listen for accepting connections from client.
-	svr.listener, err = frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
+	svr.listener, err = frpNet.ListenTcp(cfg.BindAddr, cfg.BindPort)
 	if err != nil {
 		err = fmt.Errorf("Create server listener error, %v", err)
 		return
 	}
-	log.Info("frps tcp listen on %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
+	log.Info("frps tcp listen on %s:%d", cfg.BindAddr, cfg.BindPort)
 
 	// Listen for accepting connections from client using kcp protocol.
-	if config.ServerCommonCfg.KcpBindPort > 0 {
-		svr.kcpListener, err = frpNet.ListenKcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.KcpBindPort)
+	if cfg.KcpBindPort > 0 {
+		svr.kcpListener, err = frpNet.ListenKcp(cfg.BindAddr, cfg.KcpBindPort)
 		if err != nil {
-			err = fmt.Errorf("Listen on kcp address udp [%s:%d] error: %v", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.KcpBindPort, err)
+			err = fmt.Errorf("Listen on kcp address udp [%s:%d] error: %v", cfg.BindAddr, cfg.KcpBindPort, err)
 			return
 		}
-		log.Info("frps kcp listen on udp %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
+		log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.BindPort)
 	}
 
 	// Create http vhost muxer.
-	if config.ServerCommonCfg.VhostHttpPort > 0 {
+	if cfg.VhostHttpPort > 0 {
 		var l frpNet.Listener
-		l, err = frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpPort)
+		l, err = frpNet.ListenTcp(cfg.ProxyBindAddr, cfg.VhostHttpPort)
 		if err != nil {
 			err = fmt.Errorf("Create vhost http listener error, %v", err)
 			return
@@ -105,13 +109,13 @@ func NewService() (svr *Service, err error) {
 			err = fmt.Errorf("Create vhost httpMuxer error, %v", err)
 			return
 		}
-		log.Info("http service listen on %s:%d", config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpPort)
+		log.Info("http service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
 	}
 
 	// Create https vhost muxer.
-	if config.ServerCommonCfg.VhostHttpsPort > 0 {
+	if cfg.VhostHttpsPort > 0 {
 		var l frpNet.Listener
-		l, err = frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpsPort)
+		l, err = frpNet.ListenTcp(cfg.ProxyBindAddr, cfg.VhostHttpsPort)
 		if err != nil {
 			err = fmt.Errorf("Create vhost https listener error, %v", err)
 			return
@@ -121,22 +125,38 @@ func NewService() (svr *Service, err error) {
 			err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
 			return
 		}
-		log.Info("https service listen on %s:%d", config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpsPort)
+		log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
+	}
+
+	// Create nat hole controller.
+	if cfg.BindUdpPort > 0 {
+		var nc *NatHoleController
+		addr := fmt.Sprintf("%s:%d", cfg.BindAddr, cfg.BindUdpPort)
+		nc, err = NewNatHoleController(addr)
+		if err != nil {
+			err = fmt.Errorf("Create nat hole controller error, %v", err)
+			return
+		}
+		svr.natHoleController = nc
+		log.Info("nat hole udp service listen on %s:%d", cfg.BindAddr, cfg.BindUdpPort)
 	}
 
 	// Create dashboard web server.
-	if config.ServerCommonCfg.DashboardPort > 0 {
-		err = RunDashboardServer(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.DashboardPort)
+	if cfg.DashboardPort > 0 {
+		err = RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
 		if err != nil {
 			err = fmt.Errorf("Create dashboard web server error, %v", err)
 			return
 		}
-		log.Info("Dashboard listen on %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.DashboardPort)
+		log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort)
 	}
 	return
 }
 
 func (svr *Service) Run() {
+	if svr.natHoleController != nil {
+		go svr.natHoleController.Run()
+	}
 	if config.ServerCommonCfg.KcpBindPort > 0 {
 		go svr.HandleListener(svr.kcpListener)
 	}
@@ -180,16 +200,16 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
 					}
 				case *msg.NewWorkConn:
 					svr.RegisterWorkConn(conn, m)
-				case *msg.NewVistorConn:
-					if err = svr.RegisterVistorConn(conn, m); err != nil {
+				case *msg.NewVisitorConn:
+					if err = svr.RegisterVisitorConn(conn, m); err != nil {
 						conn.Warn("%v", err)
-						msg.WriteMsg(conn, &msg.NewVistorConnResp{
+						msg.WriteMsg(conn, &msg.NewVisitorConnResp{
 							ProxyName: m.ProxyName,
 							Error:     err.Error(),
 						})
 						conn.Close()
 					} else {
-						msg.WriteMsg(conn, &msg.NewVistorConnResp{
+						msg.WriteMsg(conn, &msg.NewVisitorConnResp{
 							ProxyName: m.ProxyName,
 							Error:     "",
 						})
@@ -280,8 +300,8 @@ func (svr *Service) RegisterWorkConn(workConn frpNet.Conn, newMsg *msg.NewWorkCo
 	return
 }
 
-func (svr *Service) RegisterVistorConn(vistorConn frpNet.Conn, newMsg *msg.NewVistorConn) error {
-	return svr.vistorManager.NewConn(newMsg.ProxyName, vistorConn, newMsg.Timestamp, newMsg.SignKey,
+func (svr *Service) RegisterVisitorConn(visitorConn frpNet.Conn, newMsg *msg.NewVisitorConn) error {
+	return svr.visitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
 		newMsg.UseEncryption, newMsg.UseCompression)
 }
 

+ 15 - 1
utils/net/kcp.go

@@ -20,7 +20,7 @@ import (
 
 	"github.com/fatedier/frp/utils/log"
 
-	kcp "github.com/xtaci/kcp-go"
+	kcp "github.com/fatedier/kcp-go"
 )
 
 type KcpListener struct {
@@ -85,3 +85,17 @@ func (l *KcpListener) Close() error {
 	}
 	return nil
 }
+
+func NewKcpConnFromUdp(conn *net.UDPConn, connected bool, raddr string) (net.Conn, error) {
+	kcpConn, err := kcp.NewConnEx(1, connected, raddr, nil, 10, 3, conn)
+	if err != nil {
+		return nil, err
+	}
+	kcpConn.SetStreamMode(true)
+	kcpConn.SetWriteDelay(true)
+	kcpConn.SetNoDelay(1, 20, 2, 1)
+	kcpConn.SetMtu(1350)
+	kcpConn.SetWindowSize(1024, 1024)
+	kcpConn.SetACKNoDelay(false)
+	return kcpConn, nil
+}

+ 9 - 15
utils/version/version.go

@@ -19,37 +19,31 @@ import (
 	"strings"
 )
 
-var version string = "0.13.0"
+var version string = "0.14.0"
 
 func Full() string {
 	return version
 }
 
-func Proto(v string) int64 {
+func getSubVersion(v string, position int) int64 {
 	arr := strings.Split(v, ".")
 	if len(arr) < 3 {
 		return 0
 	}
-	res, _ := strconv.ParseInt(arr[0], 10, 64)
+	res, _ := strconv.ParseInt(arr[position], 10, 64)
 	return res
 }
 
+func Proto(v string) int64 {
+	return getSubVersion(v, 0)
+}
+
 func Major(v string) int64 {
-	arr := strings.Split(v, ".")
-	if len(arr) < 3 {
-		return 0
-	}
-	res, _ := strconv.ParseInt(arr[1], 10, 64)
-	return res
+	return getSubVersion(v, 1)
 }
 
 func Minor(v string) int64 {
-	arr := strings.Split(v, ".")
-	if len(arr) < 3 {
-		return 0
-	}
-	res, _ := strconv.ParseInt(arr[2], 10, 64)
-	return res
+	return getSubVersion(v, 2)
 }
 
 // add every case there if server will not accept client's protocol and return false

+ 7 - 1
utils/vhost/vhost.go

@@ -18,6 +18,7 @@ import (
 	"sync"
 	"time"
 
+	"github.com/fatedier/frp/utils/errors"
 	"github.com/fatedier/frp/utils/log"
 	frpNet "github.com/fatedier/frp/utils/net"
 )
@@ -162,7 +163,12 @@ func (v *VhostMuxer) handle(c frpNet.Conn) {
 	c = sConn
 
 	l.Debug("get new http request host [%s] path [%s]", name, path)
-	l.accept <- c
+	err = errors.PanicToError(func() {
+		l.accept <- c
+	})
+	if err != nil {
+		l.Warn("listener is already closed, ignore this request")
+	}
 }
 
 type Listener struct {

+ 119 - 0
vendor/github.com/armon/go-socks5/auth_test.go

@@ -0,0 +1,119 @@
+package socks5
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestNoAuth(t *testing.T) {
+	req := bytes.NewBuffer(nil)
+	req.Write([]byte{1, NoAuth})
+	var resp bytes.Buffer
+
+	s, _ := New(&Config{})
+	ctx, err := s.authenticate(&resp, req)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	if ctx.Method != NoAuth {
+		t.Fatal("Invalid Context Method")
+	}
+
+	out := resp.Bytes()
+	if !bytes.Equal(out, []byte{socks5Version, NoAuth}) {
+		t.Fatalf("bad: %v", out)
+	}
+}
+
+func TestPasswordAuth_Valid(t *testing.T) {
+	req := bytes.NewBuffer(nil)
+	req.Write([]byte{2, NoAuth, UserPassAuth})
+	req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'})
+	var resp bytes.Buffer
+
+	cred := StaticCredentials{
+		"foo": "bar",
+	}
+
+	cator := UserPassAuthenticator{Credentials: cred}
+
+	s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
+
+	ctx, err := s.authenticate(&resp, req)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	if ctx.Method != UserPassAuth {
+		t.Fatal("Invalid Context Method")
+	}
+
+	val, ok := ctx.Payload["Username"]
+	if !ok {
+		t.Fatal("Missing key Username in auth context's payload")
+	}
+
+	if val != "foo" {
+		t.Fatal("Invalid Username in auth context's payload")
+	}
+
+	out := resp.Bytes()
+	if !bytes.Equal(out, []byte{socks5Version, UserPassAuth, 1, authSuccess}) {
+		t.Fatalf("bad: %v", out)
+	}
+}
+
+func TestPasswordAuth_Invalid(t *testing.T) {
+	req := bytes.NewBuffer(nil)
+	req.Write([]byte{2, NoAuth, UserPassAuth})
+	req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'z'})
+	var resp bytes.Buffer
+
+	cred := StaticCredentials{
+		"foo": "bar",
+	}
+	cator := UserPassAuthenticator{Credentials: cred}
+	s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
+
+	ctx, err := s.authenticate(&resp, req)
+	if err != UserAuthFailed {
+		t.Fatalf("err: %v", err)
+	}
+
+	if ctx != nil {
+		t.Fatal("Invalid Context Method")
+	}
+
+	out := resp.Bytes()
+	if !bytes.Equal(out, []byte{socks5Version, UserPassAuth, 1, authFailure}) {
+		t.Fatalf("bad: %v", out)
+	}
+}
+
+func TestNoSupportedAuth(t *testing.T) {
+	req := bytes.NewBuffer(nil)
+	req.Write([]byte{1, NoAuth})
+	var resp bytes.Buffer
+
+	cred := StaticCredentials{
+		"foo": "bar",
+	}
+	cator := UserPassAuthenticator{Credentials: cred}
+
+	s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
+
+	ctx, err := s.authenticate(&resp, req)
+	if err != NoSupportedAuth {
+		t.Fatalf("err: %v", err)
+	}
+
+	if ctx != nil {
+		t.Fatal("Invalid Context Method")
+	}
+
+	out := resp.Bytes()
+	if !bytes.Equal(out, []byte{socks5Version, noAcceptable}) {
+		t.Fatalf("bad: %v", out)
+	}
+}

+ 24 - 0
vendor/github.com/armon/go-socks5/credentials_test.go

@@ -0,0 +1,24 @@
+package socks5
+
+import (
+	"testing"
+)
+
+func TestStaticCredentials(t *testing.T) {
+	creds := StaticCredentials{
+		"foo": "bar",
+		"baz": "",
+	}
+
+	if !creds.Valid("foo", "bar") {
+		t.Fatalf("expect valid")
+	}
+
+	if !creds.Valid("baz", "") {
+		t.Fatalf("expect valid")
+	}
+
+	if creds.Valid("foo", "") {
+		t.Fatalf("expect invalid")
+	}
+}

+ 169 - 0
vendor/github.com/armon/go-socks5/request_test.go

@@ -0,0 +1,169 @@
+package socks5
+
+import (
+	"bytes"
+	"encoding/binary"
+	"io"
+	"log"
+	"net"
+	"os"
+	"strings"
+	"testing"
+)
+
+type MockConn struct {
+	buf bytes.Buffer
+}
+
+func (m *MockConn) Write(b []byte) (int, error) {
+	return m.buf.Write(b)
+}
+
+func (m *MockConn) RemoteAddr() net.Addr {
+	return &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 65432}
+}
+
+func TestRequest_Connect(t *testing.T) {
+	// Create a local listener
+	l, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+	go func() {
+		conn, err := l.Accept()
+		if err != nil {
+			t.Fatalf("err: %v", err)
+		}
+		defer conn.Close()
+
+		buf := make([]byte, 4)
+		if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
+			t.Fatalf("err: %v", err)
+		}
+
+		if !bytes.Equal(buf, []byte("ping")) {
+			t.Fatalf("bad: %v", buf)
+		}
+		conn.Write([]byte("pong"))
+	}()
+	lAddr := l.Addr().(*net.TCPAddr)
+
+	// Make server
+	s := &Server{config: &Config{
+		Rules:    PermitAll(),
+		Resolver: DNSResolver{},
+		Logger:   log.New(os.Stdout, "", log.LstdFlags),
+	}}
+
+	// Create the connect request
+	buf := bytes.NewBuffer(nil)
+	buf.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
+
+	port := []byte{0, 0}
+	binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
+	buf.Write(port)
+
+	// Send a ping
+	buf.Write([]byte("ping"))
+
+	// Handle the request
+	resp := &MockConn{}
+	req, err := NewRequest(buf)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	if err := s.handleRequest(req, resp); err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	// Verify response
+	out := resp.buf.Bytes()
+	expected := []byte{
+		5,
+		0,
+		0,
+		1,
+		127, 0, 0, 1,
+		0, 0,
+		'p', 'o', 'n', 'g',
+	}
+
+	// Ignore the port for both
+	out[8] = 0
+	out[9] = 0
+
+	if !bytes.Equal(out, expected) {
+		t.Fatalf("bad: %v %v", out, expected)
+	}
+}
+
+func TestRequest_Connect_RuleFail(t *testing.T) {
+	// Create a local listener
+	l, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+	go func() {
+		conn, err := l.Accept()
+		if err != nil {
+			t.Fatalf("err: %v", err)
+		}
+		defer conn.Close()
+
+		buf := make([]byte, 4)
+		if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
+			t.Fatalf("err: %v", err)
+		}
+
+		if !bytes.Equal(buf, []byte("ping")) {
+			t.Fatalf("bad: %v", buf)
+		}
+		conn.Write([]byte("pong"))
+	}()
+	lAddr := l.Addr().(*net.TCPAddr)
+
+	// Make server
+	s := &Server{config: &Config{
+		Rules:    PermitNone(),
+		Resolver: DNSResolver{},
+		Logger:   log.New(os.Stdout, "", log.LstdFlags),
+	}}
+
+	// Create the connect request
+	buf := bytes.NewBuffer(nil)
+	buf.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
+
+	port := []byte{0, 0}
+	binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
+	buf.Write(port)
+
+	// Send a ping
+	buf.Write([]byte("ping"))
+
+	// Handle the request
+	resp := &MockConn{}
+	req, err := NewRequest(buf)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	if err := s.handleRequest(req, resp); !strings.Contains(err.Error(), "blocked by rules") {
+		t.Fatalf("err: %v", err)
+	}
+
+	// Verify response
+	out := resp.buf.Bytes()
+	expected := []byte{
+		5,
+		2,
+		0,
+		1,
+		0, 0, 0, 0,
+		0, 0,
+	}
+
+	if !bytes.Equal(out, expected) {
+		t.Fatalf("bad: %v %v", out, expected)
+	}
+}

+ 21 - 0
vendor/github.com/armon/go-socks5/resolver_test.go

@@ -0,0 +1,21 @@
+package socks5
+
+import (
+	"testing"
+
+	"golang.org/x/net/context"
+)
+
+func TestDNSResolver(t *testing.T) {
+	d := DNSResolver{}
+	ctx := context.Background()
+
+	_, addr, err := d.Resolve(ctx, "localhost")
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	if !addr.IsLoopback() {
+		t.Fatalf("expected loopback")
+	}
+}

+ 24 - 0
vendor/github.com/armon/go-socks5/ruleset_test.go

@@ -0,0 +1,24 @@
+package socks5
+
+import (
+	"testing"
+
+	"golang.org/x/net/context"
+)
+
+func TestPermitCommand(t *testing.T) {
+	ctx := context.Background()
+	r := &PermitCommand{true, false, false}
+
+	if _, ok := r.Allow(ctx, &Request{Command: ConnectCommand}); !ok {
+		t.Fatalf("expect connect")
+	}
+
+	if _, ok := r.Allow(ctx, &Request{Command: BindCommand}); ok {
+		t.Fatalf("do not expect bind")
+	}
+
+	if _, ok := r.Allow(ctx, &Request{Command: AssociateCommand}); ok {
+		t.Fatalf("do not expect associate")
+	}
+}

+ 110 - 0
vendor/github.com/armon/go-socks5/socks5_test.go

@@ -0,0 +1,110 @@
+package socks5
+
+import (
+	"bytes"
+	"encoding/binary"
+	"io"
+	"log"
+	"net"
+	"os"
+	"testing"
+	"time"
+)
+
+func TestSOCKS5_Connect(t *testing.T) {
+	// Create a local listener
+	l, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+	go func() {
+		conn, err := l.Accept()
+		if err != nil {
+			t.Fatalf("err: %v", err)
+		}
+		defer conn.Close()
+
+		buf := make([]byte, 4)
+		if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
+			t.Fatalf("err: %v", err)
+		}
+
+		if !bytes.Equal(buf, []byte("ping")) {
+			t.Fatalf("bad: %v", buf)
+		}
+		conn.Write([]byte("pong"))
+	}()
+	lAddr := l.Addr().(*net.TCPAddr)
+
+	// Create a socks server
+	creds := StaticCredentials{
+		"foo": "bar",
+	}
+	cator := UserPassAuthenticator{Credentials: creds}
+	conf := &Config{
+		AuthMethods: []Authenticator{cator},
+		Logger:      log.New(os.Stdout, "", log.LstdFlags),
+	}
+	serv, err := New(conf)
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	// Start listening
+	go func() {
+		if err := serv.ListenAndServe("tcp", "127.0.0.1:12365"); err != nil {
+			t.Fatalf("err: %v", err)
+		}
+	}()
+	time.Sleep(10 * time.Millisecond)
+
+	// Get a local conn
+	conn, err := net.Dial("tcp", "127.0.0.1:12365")
+	if err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	// Connect, auth and connec to local
+	req := bytes.NewBuffer(nil)
+	req.Write([]byte{5})
+	req.Write([]byte{2, NoAuth, UserPassAuth})
+	req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'})
+	req.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
+
+	port := []byte{0, 0}
+	binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
+	req.Write(port)
+
+	// Send a ping
+	req.Write([]byte("ping"))
+
+	// Send all the bytes
+	conn.Write(req.Bytes())
+
+	// Verify response
+	expected := []byte{
+		socks5Version, UserPassAuth,
+		1, authSuccess,
+		5,
+		0,
+		0,
+		1,
+		127, 0, 0, 1,
+		0, 0,
+		'p', 'o', 'n', 'g',
+	}
+	out := make([]byte, len(expected))
+
+	conn.SetDeadline(time.Now().Add(time.Second))
+	if _, err := io.ReadAtLeast(conn, out, len(out)); err != nil {
+		t.Fatalf("err: %v", err)
+	}
+
+	// Ignore the port
+	out[12] = 0
+	out[13] = 0
+
+	if !bytes.Equal(out, expected) {
+		t.Fatalf("bad: %v", out)
+	}
+}

+ 22 - 0
vendor/github.com/davecgh/go-spew/.gitignore

@@ -0,0 +1,22 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe

+ 14 - 0
vendor/github.com/davecgh/go-spew/.travis.yml

@@ -0,0 +1,14 @@
+language: go
+go:
+    - 1.5.4
+    - 1.6.3
+    - 1.7
+install:
+    - go get -v golang.org/x/tools/cmd/cover
+script:
+    - go test -v -tags=safe ./spew
+    - go test -v -tags=testcgo ./spew -covermode=count -coverprofile=profile.cov
+after_success:
+    - go get -v github.com/mattn/goveralls
+    - export PATH=$PATH:$HOME/gopath/bin
+    - goveralls -coverprofile=profile.cov -service=travis-ci

+ 205 - 0
vendor/github.com/davecgh/go-spew/README.md

@@ -0,0 +1,205 @@
+go-spew
+=======
+
+[![Build Status](https://img.shields.io/travis/davecgh/go-spew.svg)]
+(https://travis-ci.org/davecgh/go-spew) [![ISC License]
+(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) [![Coverage Status]
+(https://img.shields.io/coveralls/davecgh/go-spew.svg)]
+(https://coveralls.io/r/davecgh/go-spew?branch=master)
+
+
+Go-spew implements a deep pretty printer for Go data structures to aid in
+debugging.  A comprehensive suite of tests with 100% test coverage is provided
+to ensure proper functionality.  See `test_coverage.txt` for the gocov coverage
+report.  Go-spew is licensed under the liberal ISC license, so it may be used in
+open source or commercial projects.
+
+If you're interested in reading about how this package came to life and some
+of the challenges involved in providing a deep pretty printer, there is a blog
+post about it
+[here](https://web.archive.org/web/20160304013555/https://blog.cyphertite.com/go-spew-a-journey-into-dumping-go-data-structures/).
+
+## Documentation
+
+[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)]
+(http://godoc.org/github.com/davecgh/go-spew/spew)
+
+Full `go doc` style documentation for the project can be viewed online without
+installing this package by using the excellent GoDoc site here:
+http://godoc.org/github.com/davecgh/go-spew/spew
+
+You can also view the documentation locally once the package is installed with
+the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
+http://localhost:6060/pkg/github.com/davecgh/go-spew/spew
+
+## Installation
+
+```bash
+$ go get -u github.com/davecgh/go-spew/spew
+```
+
+## Quick Start
+
+Add this import line to the file you're working in:
+
+```Go
+import "github.com/davecgh/go-spew/spew"
+```
+
+To dump a variable with full newlines, indentation, type, and pointer
+information use Dump, Fdump, or Sdump:
+
+```Go
+spew.Dump(myVar1, myVar2, ...)
+spew.Fdump(someWriter, myVar1, myVar2, ...)
+str := spew.Sdump(myVar1, myVar2, ...)
+```
+
+Alternatively, if you would prefer to use format strings with a compacted inline
+printing style, use the convenience wrappers Printf, Fprintf, etc with %v (most
+compact), %+v (adds pointer addresses), %#v (adds types), or %#+v (adds types
+and pointer addresses): 
+
+```Go
+spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
+spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
+spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
+spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
+```
+
+## Debugging a Web Application Example
+
+Here is an example of how you can use `spew.Sdump()` to help debug a web application. Please be sure to wrap your output using the `html.EscapeString()` function for safety reasons. You should also only use this debugging technique in a development environment, never in production.
+
+```Go
+package main
+
+import (
+    "fmt"
+    "html"
+    "net/http"
+
+    "github.com/davecgh/go-spew/spew"
+)
+
+func handler(w http.ResponseWriter, r *http.Request) {
+    w.Header().Set("Content-Type", "text/html")
+    fmt.Fprintf(w, "Hi there, %s!", r.URL.Path[1:])
+    fmt.Fprintf(w, "<!--\n" + html.EscapeString(spew.Sdump(w)) + "\n-->")
+}
+
+func main() {
+    http.HandleFunc("/", handler)
+    http.ListenAndServe(":8080", nil)
+}
+```
+
+## Sample Dump Output
+
+```
+(main.Foo) {
+ unexportedField: (*main.Bar)(0xf84002e210)({
+  flag: (main.Flag) flagTwo,
+  data: (uintptr) <nil>
+ }),
+ ExportedField: (map[interface {}]interface {}) {
+  (string) "one": (bool) true
+ }
+}
+([]uint8) {
+ 00000000  11 12 13 14 15 16 17 18  19 1a 1b 1c 1d 1e 1f 20  |............... |
+ 00000010  21 22 23 24 25 26 27 28  29 2a 2b 2c 2d 2e 2f 30  |!"#$%&'()*+,-./0|
+ 00000020  31 32                                             |12|
+}
+```
+
+## Sample Formatter Output
+
+Double pointer to a uint8:
+```
+	  %v: <**>5
+	 %+v: <**>(0xf8400420d0->0xf8400420c8)5
+	 %#v: (**uint8)5
+	%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
+```
+
+Pointer to circular struct with a uint8 field and a pointer to itself:
+```
+	  %v: <*>{1 <*><shown>}
+	 %+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
+	 %#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
+	%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
+```
+
+## Configuration Options
+
+Configuration of spew is handled by fields in the ConfigState type. For
+convenience, all of the top-level functions use a global state available via the
+spew.Config global.
+
+It is also possible to create a ConfigState instance that provides methods
+equivalent to the top-level functions. This allows concurrent configuration
+options. See the ConfigState documentation for more details.
+
+```
+* Indent
+	String to use for each indentation level for Dump functions.
+	It is a single space by default.  A popular alternative is "\t".
+
+* MaxDepth
+	Maximum number of levels to descend into nested data structures.
+	There is no limit by default.
+
+* DisableMethods
+	Disables invocation of error and Stringer interface methods.
+	Method invocation is enabled by default.
+
+* DisablePointerMethods
+	Disables invocation of error and Stringer interface methods on types
+	which only accept pointer receivers from non-pointer variables.  This option
+	relies on access to the unsafe package, so it will not have any effect when
+	running in environments without access to the unsafe package such as Google
+	App Engine or with the "safe" build tag specified.
+	Pointer method invocation is enabled by default.
+
+* DisablePointerAddresses
+	DisablePointerAddresses specifies whether to disable the printing of
+	pointer addresses. This is useful when diffing data structures in tests.
+
+* DisableCapacities
+	DisableCapacities specifies whether to disable the printing of capacities
+	for arrays, slices, maps and channels. This is useful when diffing data
+	structures in tests.
+
+* ContinueOnMethod
+	Enables recursion into types after invoking error and Stringer interface
+	methods. Recursion after method invocation is disabled by default.
+
+* SortKeys
+	Specifies map keys should be sorted before being printed. Use
+	this to have a more deterministic, diffable output.  Note that
+	only native types (bool, int, uint, floats, uintptr and string)
+	and types which implement error or Stringer interfaces are supported,
+	with other types sorted according to the reflect.Value.String() output
+	which guarantees display stability.  Natural map order is used by
+	default.
+
+* SpewKeys
+	SpewKeys specifies that, as a last resort attempt, map keys should be
+	spewed to strings and sorted by those strings.  This is only considered
+	if SortKeys is true.
+
+```
+
+## Unsafe Package Dependency
+
+This package relies on the unsafe package to perform some of the more advanced
+features, however it also supports a "limited" mode which allows it to work in
+environments where the unsafe package is not available.  By default, it will
+operate in this mode on Google App Engine and when compiled with GopherJS.  The
+"safe" build tag may also be specified to force the package to build without
+using the unsafe package.
+
+## License
+
+Go-spew is licensed under the [copyfree](http://copyfree.org) ISC License.

+ 22 - 0
vendor/github.com/davecgh/go-spew/cov_report.sh

@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# This script uses gocov to generate a test coverage report.
+# The gocov tool my be obtained with the following command:
+#   go get github.com/axw/gocov/gocov
+#
+# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
+
+# Check for gocov.
+if ! type gocov >/dev/null 2>&1; then
+	echo >&2 "This script requires the gocov tool."
+	echo >&2 "You may obtain it with the following command:"
+	echo >&2 "go get github.com/axw/gocov/gocov"
+	exit 1
+fi
+
+# Only run the cgo tests if gcc is installed.
+if type gcc >/dev/null 2>&1; then
+	(cd spew && gocov test -tags testcgo | gocov report)
+else
+	(cd spew && gocov test | gocov report)
+fi

+ 298 - 0
vendor/github.com/davecgh/go-spew/spew/common_test.go

@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package spew_test
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+
+	"github.com/davecgh/go-spew/spew"
+)
+
+// custom type to test Stinger interface on non-pointer receiver.
+type stringer string
+
+// String implements the Stringer interface for testing invocation of custom
+// stringers on types with non-pointer receivers.
+func (s stringer) String() string {
+	return "stringer " + string(s)
+}
+
+// custom type to test Stinger interface on pointer receiver.
+type pstringer string
+
+// String implements the Stringer interface for testing invocation of custom
+// stringers on types with only pointer receivers.
+func (s *pstringer) String() string {
+	return "stringer " + string(*s)
+}
+
+// xref1 and xref2 are cross referencing structs for testing circular reference
+// detection.
+type xref1 struct {
+	ps2 *xref2
+}
+type xref2 struct {
+	ps1 *xref1
+}
+
+// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular
+// reference for testing detection.
+type indirCir1 struct {
+	ps2 *indirCir2
+}
+type indirCir2 struct {
+	ps3 *indirCir3
+}
+type indirCir3 struct {
+	ps1 *indirCir1
+}
+
+// embed is used to test embedded structures.
+type embed struct {
+	a string
+}
+
+// embedwrap is used to test embedded structures.
+type embedwrap struct {
+	*embed
+	e *embed
+}
+
+// panicer is used to intentionally cause a panic for testing spew properly
+// handles them
+type panicer int
+
+func (p panicer) String() string {
+	panic("test panic")
+}
+
+// customError is used to test custom error interface invocation.
+type customError int
+
+func (e customError) Error() string {
+	return fmt.Sprintf("error: %d", int(e))
+}
+
+// stringizeWants converts a slice of wanted test output into a format suitable
+// for a test error message.
+func stringizeWants(wants []string) string {
+	s := ""
+	for i, want := range wants {
+		if i > 0 {
+			s += fmt.Sprintf("want%d: %s", i+1, want)
+		} else {
+			s += "want: " + want
+		}
+	}
+	return s
+}
+
+// testFailed returns whether or not a test failed by checking if the result
+// of the test is in the slice of wanted strings.
+func testFailed(result string, wants []string) bool {
+	for _, want := range wants {
+		if result == want {
+			return false
+		}
+	}
+	return true
+}
+
+type sortableStruct struct {
+	x int
+}
+
+func (ss sortableStruct) String() string {
+	return fmt.Sprintf("ss.%d", ss.x)
+}
+
+type unsortableStruct struct {
+	x int
+}
+
+type sortTestCase struct {
+	input    []reflect.Value
+	expected []reflect.Value
+}
+
+func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) {
+	getInterfaces := func(values []reflect.Value) []interface{} {
+		interfaces := []interface{}{}
+		for _, v := range values {
+			interfaces = append(interfaces, v.Interface())
+		}
+		return interfaces
+	}
+
+	for _, test := range tests {
+		spew.SortValues(test.input, cs)
+		// reflect.DeepEqual cannot really make sense of reflect.Value,
+		// probably because of all the pointer tricks. For instance,
+		// v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{}
+		// instead.
+		input := getInterfaces(test.input)
+		expected := getInterfaces(test.expected)
+		if !reflect.DeepEqual(input, expected) {
+			t.Errorf("Sort mismatch:\n %v != %v", input, expected)
+		}
+	}
+}
+
+// TestSortValues ensures the sort functionality for relect.Value based sorting
+// works as intended.
+func TestSortValues(t *testing.T) {
+	v := reflect.ValueOf
+
+	a := v("a")
+	b := v("b")
+	c := v("c")
+	embedA := v(embed{"a"})
+	embedB := v(embed{"b"})
+	embedC := v(embed{"c"})
+	tests := []sortTestCase{
+		// No values.
+		{
+			[]reflect.Value{},
+			[]reflect.Value{},
+		},
+		// Bools.
+		{
+			[]reflect.Value{v(false), v(true), v(false)},
+			[]reflect.Value{v(false), v(false), v(true)},
+		},
+		// Ints.
+		{
+			[]reflect.Value{v(2), v(1), v(3)},
+			[]reflect.Value{v(1), v(2), v(3)},
+		},
+		// Uints.
+		{
+			[]reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))},
+			[]reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))},
+		},
+		// Floats.
+		{
+			[]reflect.Value{v(2.0), v(1.0), v(3.0)},
+			[]reflect.Value{v(1.0), v(2.0), v(3.0)},
+		},
+		// Strings.
+		{
+			[]reflect.Value{b, a, c},
+			[]reflect.Value{a, b, c},
+		},
+		// Array
+		{
+			[]reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})},
+			[]reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})},
+		},
+		// Uintptrs.
+		{
+			[]reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))},
+			[]reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))},
+		},
+		// SortableStructs.
+		{
+			// Note: not sorted - DisableMethods is set.
+			[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
+			[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
+		},
+		// UnsortableStructs.
+		{
+			// Note: not sorted - SpewKeys is false.
+			[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
+			[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
+		},
+		// Invalid.
+		{
+			[]reflect.Value{embedB, embedA, embedC},
+			[]reflect.Value{embedB, embedA, embedC},
+		},
+	}
+	cs := spew.ConfigState{DisableMethods: true, SpewKeys: false}
+	helpTestSortValues(tests, &cs, t)
+}
+
+// TestSortValuesWithMethods ensures the sort functionality for relect.Value
+// based sorting works as intended when using string methods.
+func TestSortValuesWithMethods(t *testing.T) {
+	v := reflect.ValueOf
+
+	a := v("a")
+	b := v("b")
+	c := v("c")
+	tests := []sortTestCase{
+		// Ints.
+		{
+			[]reflect.Value{v(2), v(1), v(3)},
+			[]reflect.Value{v(1), v(2), v(3)},
+		},
+		// Strings.
+		{
+			[]reflect.Value{b, a, c},
+			[]reflect.Value{a, b, c},
+		},
+		// SortableStructs.
+		{
+			[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
+			[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
+		},
+		// UnsortableStructs.
+		{
+			// Note: not sorted - SpewKeys is false.
+			[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
+			[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
+		},
+	}
+	cs := spew.ConfigState{DisableMethods: false, SpewKeys: false}
+	helpTestSortValues(tests, &cs, t)
+}
+
+// TestSortValuesWithSpew ensures the sort functionality for relect.Value
+// based sorting works as intended when using spew to stringify keys.
+func TestSortValuesWithSpew(t *testing.T) {
+	v := reflect.ValueOf
+
+	a := v("a")
+	b := v("b")
+	c := v("c")
+	tests := []sortTestCase{
+		// Ints.
+		{
+			[]reflect.Value{v(2), v(1), v(3)},
+			[]reflect.Value{v(1), v(2), v(3)},
+		},
+		// Strings.
+		{
+			[]reflect.Value{b, a, c},
+			[]reflect.Value{a, b, c},
+		},
+		// SortableStructs.
+		{
+			[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
+			[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
+		},
+		// UnsortableStructs.
+		{
+			[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
+			[]reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})},
+		},
+	}
+	cs := spew.ConfigState{DisableMethods: true, SpewKeys: true}
+	helpTestSortValues(tests, &cs, t)
+}

+ 1042 - 0
vendor/github.com/davecgh/go-spew/spew/dump_test.go

@@ -0,0 +1,1042 @@
+/*
+ * Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+Test Summary:
+NOTE: For each test, a nil pointer, a single pointer and double pointer to the
+base test element are also tested to ensure proper indirection across all types.
+
+- Max int8, int16, int32, int64, int
+- Max uint8, uint16, uint32, uint64, uint
+- Boolean true and false
+- Standard complex64 and complex128
+- Array containing standard ints
+- Array containing type with custom formatter on pointer receiver only
+- Array containing interfaces
+- Array containing bytes
+- Slice containing standard float32 values
+- Slice containing type with custom formatter on pointer receiver only
+- Slice containing interfaces
+- Slice containing bytes
+- Nil slice
+- Standard string
+- Nil interface
+- Sub-interface
+- Map with string keys and int vals
+- Map with custom formatter type on pointer receiver only keys and vals
+- Map with interface keys and values
+- Map with nil interface value
+- Struct with primitives
+- Struct that contains another struct
+- Struct that contains custom type with Stringer pointer interface via both
+  exported and unexported fields
+- Struct that contains embedded struct and field to same struct
+- Uintptr to 0 (null pointer)
+- Uintptr address of real variable
+- Unsafe.Pointer to 0 (null pointer)
+- Unsafe.Pointer to address of real variable
+- Nil channel
+- Standard int channel
+- Function with no params and no returns
+- Function with param and no returns
+- Function with multiple params and multiple returns
+- Struct that is circular through self referencing
+- Structs that are circular through cross referencing
+- Structs that are indirectly circular
+- Type that panics in its Stringer interface
+*/
+
+package spew_test
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+	"unsafe"
+
+	"github.com/davecgh/go-spew/spew"
+)
+
+// dumpTest is used to describe a test to be performed against the Dump method.
+type dumpTest struct {
+	in    interface{}
+	wants []string
+}
+
+// dumpTests houses all of the tests to be performed against the Dump method.
+var dumpTests = make([]dumpTest, 0)
+
+// addDumpTest is a helper method to append the passed input and desired result
+// to dumpTests
+func addDumpTest(in interface{}, wants ...string) {
+	test := dumpTest{in, wants}
+	dumpTests = append(dumpTests, test)
+}
+
+func addIntDumpTests() {
+	// Max int8.
+	v := int8(127)
+	nv := (*int8)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "int8"
+	vs := "127"
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+
+	// Max int16.
+	v2 := int16(32767)
+	nv2 := (*int16)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "int16"
+	v2s := "32767"
+	addDumpTest(v2, "("+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n")
+	addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n")
+	addDumpTest(nv2, "(*"+v2t+")(<nil>)\n")
+
+	// Max int32.
+	v3 := int32(2147483647)
+	nv3 := (*int32)(nil)
+	pv3 := &v3
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "int32"
+	v3s := "2147483647"
+	addDumpTest(v3, "("+v3t+") "+v3s+"\n")
+	addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3s+")\n")
+	addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3s+")\n")
+	addDumpTest(nv3, "(*"+v3t+")(<nil>)\n")
+
+	// Max int64.
+	v4 := int64(9223372036854775807)
+	nv4 := (*int64)(nil)
+	pv4 := &v4
+	v4Addr := fmt.Sprintf("%p", pv4)
+	pv4Addr := fmt.Sprintf("%p", &pv4)
+	v4t := "int64"
+	v4s := "9223372036854775807"
+	addDumpTest(v4, "("+v4t+") "+v4s+"\n")
+	addDumpTest(pv4, "(*"+v4t+")("+v4Addr+")("+v4s+")\n")
+	addDumpTest(&pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")("+v4s+")\n")
+	addDumpTest(nv4, "(*"+v4t+")(<nil>)\n")
+
+	// Max int.
+	v5 := int(2147483647)
+	nv5 := (*int)(nil)
+	pv5 := &v5
+	v5Addr := fmt.Sprintf("%p", pv5)
+	pv5Addr := fmt.Sprintf("%p", &pv5)
+	v5t := "int"
+	v5s := "2147483647"
+	addDumpTest(v5, "("+v5t+") "+v5s+"\n")
+	addDumpTest(pv5, "(*"+v5t+")("+v5Addr+")("+v5s+")\n")
+	addDumpTest(&pv5, "(**"+v5t+")("+pv5Addr+"->"+v5Addr+")("+v5s+")\n")
+	addDumpTest(nv5, "(*"+v5t+")(<nil>)\n")
+}
+
+func addUintDumpTests() {
+	// Max uint8.
+	v := uint8(255)
+	nv := (*uint8)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "uint8"
+	vs := "255"
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+
+	// Max uint16.
+	v2 := uint16(65535)
+	nv2 := (*uint16)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "uint16"
+	v2s := "65535"
+	addDumpTest(v2, "("+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n")
+	addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n")
+	addDumpTest(nv2, "(*"+v2t+")(<nil>)\n")
+
+	// Max uint32.
+	v3 := uint32(4294967295)
+	nv3 := (*uint32)(nil)
+	pv3 := &v3
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "uint32"
+	v3s := "4294967295"
+	addDumpTest(v3, "("+v3t+") "+v3s+"\n")
+	addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3s+")\n")
+	addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3s+")\n")
+	addDumpTest(nv3, "(*"+v3t+")(<nil>)\n")
+
+	// Max uint64.
+	v4 := uint64(18446744073709551615)
+	nv4 := (*uint64)(nil)
+	pv4 := &v4
+	v4Addr := fmt.Sprintf("%p", pv4)
+	pv4Addr := fmt.Sprintf("%p", &pv4)
+	v4t := "uint64"
+	v4s := "18446744073709551615"
+	addDumpTest(v4, "("+v4t+") "+v4s+"\n")
+	addDumpTest(pv4, "(*"+v4t+")("+v4Addr+")("+v4s+")\n")
+	addDumpTest(&pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")("+v4s+")\n")
+	addDumpTest(nv4, "(*"+v4t+")(<nil>)\n")
+
+	// Max uint.
+	v5 := uint(4294967295)
+	nv5 := (*uint)(nil)
+	pv5 := &v5
+	v5Addr := fmt.Sprintf("%p", pv5)
+	pv5Addr := fmt.Sprintf("%p", &pv5)
+	v5t := "uint"
+	v5s := "4294967295"
+	addDumpTest(v5, "("+v5t+") "+v5s+"\n")
+	addDumpTest(pv5, "(*"+v5t+")("+v5Addr+")("+v5s+")\n")
+	addDumpTest(&pv5, "(**"+v5t+")("+pv5Addr+"->"+v5Addr+")("+v5s+")\n")
+	addDumpTest(nv5, "(*"+v5t+")(<nil>)\n")
+}
+
+func addBoolDumpTests() {
+	// Boolean true.
+	v := bool(true)
+	nv := (*bool)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "bool"
+	vs := "true"
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+
+	// Boolean false.
+	v2 := bool(false)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "bool"
+	v2s := "false"
+	addDumpTest(v2, "("+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n")
+	addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n")
+}
+
+func addFloatDumpTests() {
+	// Standard float32.
+	v := float32(3.1415)
+	nv := (*float32)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "float32"
+	vs := "3.1415"
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+
+	// Standard float64.
+	v2 := float64(3.1415926)
+	nv2 := (*float64)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "float64"
+	v2s := "3.1415926"
+	addDumpTest(v2, "("+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n")
+	addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n")
+	addDumpTest(nv2, "(*"+v2t+")(<nil>)\n")
+}
+
+func addComplexDumpTests() {
+	// Standard complex64.
+	v := complex(float32(6), -2)
+	nv := (*complex64)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "complex64"
+	vs := "(6-2i)"
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+
+	// Standard complex128.
+	v2 := complex(float64(-6), 2)
+	nv2 := (*complex128)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "complex128"
+	v2s := "(-6+2i)"
+	addDumpTest(v2, "("+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n")
+	addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n")
+	addDumpTest(nv2, "(*"+v2t+")(<nil>)\n")
+}
+
+func addArrayDumpTests() {
+	// Array containing standard ints.
+	v := [3]int{1, 2, 3}
+	vLen := fmt.Sprintf("%d", len(v))
+	vCap := fmt.Sprintf("%d", cap(v))
+	nv := (*[3]int)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "int"
+	vs := "(len=" + vLen + " cap=" + vCap + ") {\n (" + vt + ") 1,\n (" +
+		vt + ") 2,\n (" + vt + ") 3\n}"
+	addDumpTest(v, "([3]"+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*[3]"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**[3]"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*[3]"+vt+")(<nil>)\n")
+
+	// Array containing type with custom formatter on pointer receiver only.
+	v2i0 := pstringer("1")
+	v2i1 := pstringer("2")
+	v2i2 := pstringer("3")
+	v2 := [3]pstringer{v2i0, v2i1, v2i2}
+	v2i0Len := fmt.Sprintf("%d", len(v2i0))
+	v2i1Len := fmt.Sprintf("%d", len(v2i1))
+	v2i2Len := fmt.Sprintf("%d", len(v2i2))
+	v2Len := fmt.Sprintf("%d", len(v2))
+	v2Cap := fmt.Sprintf("%d", cap(v2))
+	nv2 := (*[3]pstringer)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "spew_test.pstringer"
+	v2sp := "(len=" + v2Len + " cap=" + v2Cap + ") {\n (" + v2t +
+		") (len=" + v2i0Len + ") stringer 1,\n (" + v2t +
+		") (len=" + v2i1Len + ") stringer 2,\n (" + v2t +
+		") (len=" + v2i2Len + ") " + "stringer 3\n}"
+	v2s := v2sp
+	if spew.UnsafeDisabled {
+		v2s = "(len=" + v2Len + " cap=" + v2Cap + ") {\n (" + v2t +
+			") (len=" + v2i0Len + ") \"1\",\n (" + v2t + ") (len=" +
+			v2i1Len + ") \"2\",\n (" + v2t + ") (len=" + v2i2Len +
+			") " + "\"3\"\n}"
+	}
+	addDumpTest(v2, "([3]"+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*[3]"+v2t+")("+v2Addr+")("+v2sp+")\n")
+	addDumpTest(&pv2, "(**[3]"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2sp+")\n")
+	addDumpTest(nv2, "(*[3]"+v2t+")(<nil>)\n")
+
+	// Array containing interfaces.
+	v3i0 := "one"
+	v3 := [3]interface{}{v3i0, int(2), uint(3)}
+	v3i0Len := fmt.Sprintf("%d", len(v3i0))
+	v3Len := fmt.Sprintf("%d", len(v3))
+	v3Cap := fmt.Sprintf("%d", cap(v3))
+	nv3 := (*[3]interface{})(nil)
+	pv3 := &v3
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "[3]interface {}"
+	v3t2 := "string"
+	v3t3 := "int"
+	v3t4 := "uint"
+	v3s := "(len=" + v3Len + " cap=" + v3Cap + ") {\n (" + v3t2 + ") " +
+		"(len=" + v3i0Len + ") \"one\",\n (" + v3t3 + ") 2,\n (" +
+		v3t4 + ") 3\n}"
+	addDumpTest(v3, "("+v3t+") "+v3s+"\n")
+	addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3s+")\n")
+	addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3s+")\n")
+	addDumpTest(nv3, "(*"+v3t+")(<nil>)\n")
+
+	// Array containing bytes.
+	v4 := [34]byte{
+		0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+		0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+		0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+		0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
+		0x31, 0x32,
+	}
+	v4Len := fmt.Sprintf("%d", len(v4))
+	v4Cap := fmt.Sprintf("%d", cap(v4))
+	nv4 := (*[34]byte)(nil)
+	pv4 := &v4
+	v4Addr := fmt.Sprintf("%p", pv4)
+	pv4Addr := fmt.Sprintf("%p", &pv4)
+	v4t := "[34]uint8"
+	v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
+		"{\n 00000000  11 12 13 14 15 16 17 18  19 1a 1b 1c 1d 1e 1f 20" +
+		"  |............... |\n" +
+		" 00000010  21 22 23 24 25 26 27 28  29 2a 2b 2c 2d 2e 2f 30" +
+		"  |!\"#$%&'()*+,-./0|\n" +
+		" 00000020  31 32                                           " +
+		"  |12|\n}"
+	addDumpTest(v4, "("+v4t+") "+v4s+"\n")
+	addDumpTest(pv4, "(*"+v4t+")("+v4Addr+")("+v4s+")\n")
+	addDumpTest(&pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")("+v4s+")\n")
+	addDumpTest(nv4, "(*"+v4t+")(<nil>)\n")
+}
+
+func addSliceDumpTests() {
+	// Slice containing standard float32 values.
+	v := []float32{3.14, 6.28, 12.56}
+	vLen := fmt.Sprintf("%d", len(v))
+	vCap := fmt.Sprintf("%d", cap(v))
+	nv := (*[]float32)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "float32"
+	vs := "(len=" + vLen + " cap=" + vCap + ") {\n (" + vt + ") 3.14,\n (" +
+		vt + ") 6.28,\n (" + vt + ") 12.56\n}"
+	addDumpTest(v, "([]"+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*[]"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**[]"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*[]"+vt+")(<nil>)\n")
+
+	// Slice containing type with custom formatter on pointer receiver only.
+	v2i0 := pstringer("1")
+	v2i1 := pstringer("2")
+	v2i2 := pstringer("3")
+	v2 := []pstringer{v2i0, v2i1, v2i2}
+	v2i0Len := fmt.Sprintf("%d", len(v2i0))
+	v2i1Len := fmt.Sprintf("%d", len(v2i1))
+	v2i2Len := fmt.Sprintf("%d", len(v2i2))
+	v2Len := fmt.Sprintf("%d", len(v2))
+	v2Cap := fmt.Sprintf("%d", cap(v2))
+	nv2 := (*[]pstringer)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "spew_test.pstringer"
+	v2s := "(len=" + v2Len + " cap=" + v2Cap + ") {\n (" + v2t + ") (len=" +
+		v2i0Len + ") stringer 1,\n (" + v2t + ") (len=" + v2i1Len +
+		") stringer 2,\n (" + v2t + ") (len=" + v2i2Len + ") " +
+		"stringer 3\n}"
+	addDumpTest(v2, "([]"+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*[]"+v2t+")("+v2Addr+")("+v2s+")\n")
+	addDumpTest(&pv2, "(**[]"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n")
+	addDumpTest(nv2, "(*[]"+v2t+")(<nil>)\n")
+
+	// Slice containing interfaces.
+	v3i0 := "one"
+	v3 := []interface{}{v3i0, int(2), uint(3), nil}
+	v3i0Len := fmt.Sprintf("%d", len(v3i0))
+	v3Len := fmt.Sprintf("%d", len(v3))
+	v3Cap := fmt.Sprintf("%d", cap(v3))
+	nv3 := (*[]interface{})(nil)
+	pv3 := &v3
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "[]interface {}"
+	v3t2 := "string"
+	v3t3 := "int"
+	v3t4 := "uint"
+	v3t5 := "interface {}"
+	v3s := "(len=" + v3Len + " cap=" + v3Cap + ") {\n (" + v3t2 + ") " +
+		"(len=" + v3i0Len + ") \"one\",\n (" + v3t3 + ") 2,\n (" +
+		v3t4 + ") 3,\n (" + v3t5 + ") <nil>\n}"
+	addDumpTest(v3, "("+v3t+") "+v3s+"\n")
+	addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3s+")\n")
+	addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3s+")\n")
+	addDumpTest(nv3, "(*"+v3t+")(<nil>)\n")
+
+	// Slice containing bytes.
+	v4 := []byte{
+		0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+		0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+		0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+		0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
+		0x31, 0x32,
+	}
+	v4Len := fmt.Sprintf("%d", len(v4))
+	v4Cap := fmt.Sprintf("%d", cap(v4))
+	nv4 := (*[]byte)(nil)
+	pv4 := &v4
+	v4Addr := fmt.Sprintf("%p", pv4)
+	pv4Addr := fmt.Sprintf("%p", &pv4)
+	v4t := "[]uint8"
+	v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
+		"{\n 00000000  11 12 13 14 15 16 17 18  19 1a 1b 1c 1d 1e 1f 20" +
+		"  |............... |\n" +
+		" 00000010  21 22 23 24 25 26 27 28  29 2a 2b 2c 2d 2e 2f 30" +
+		"  |!\"#$%&'()*+,-./0|\n" +
+		" 00000020  31 32                                           " +
+		"  |12|\n}"
+	addDumpTest(v4, "("+v4t+") "+v4s+"\n")
+	addDumpTest(pv4, "(*"+v4t+")("+v4Addr+")("+v4s+")\n")
+	addDumpTest(&pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")("+v4s+")\n")
+	addDumpTest(nv4, "(*"+v4t+")(<nil>)\n")
+
+	// Nil slice.
+	v5 := []int(nil)
+	nv5 := (*[]int)(nil)
+	pv5 := &v5
+	v5Addr := fmt.Sprintf("%p", pv5)
+	pv5Addr := fmt.Sprintf("%p", &pv5)
+	v5t := "[]int"
+	v5s := "<nil>"
+	addDumpTest(v5, "("+v5t+") "+v5s+"\n")
+	addDumpTest(pv5, "(*"+v5t+")("+v5Addr+")("+v5s+")\n")
+	addDumpTest(&pv5, "(**"+v5t+")("+pv5Addr+"->"+v5Addr+")("+v5s+")\n")
+	addDumpTest(nv5, "(*"+v5t+")(<nil>)\n")
+}
+
+func addStringDumpTests() {
+	// Standard string.
+	v := "test"
+	vLen := fmt.Sprintf("%d", len(v))
+	nv := (*string)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "string"
+	vs := "(len=" + vLen + ") \"test\""
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+}
+
+func addInterfaceDumpTests() {
+	// Nil interface.
+	var v interface{}
+	nv := (*interface{})(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "interface {}"
+	vs := "<nil>"
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+
+	// Sub-interface.
+	v2 := interface{}(uint16(65535))
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "uint16"
+	v2s := "65535"
+	addDumpTest(v2, "("+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n")
+	addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n")
+}
+
+func addMapDumpTests() {
+	// Map with string keys and int vals.
+	k := "one"
+	kk := "two"
+	m := map[string]int{k: 1, kk: 2}
+	klen := fmt.Sprintf("%d", len(k)) // not kLen to shut golint up
+	kkLen := fmt.Sprintf("%d", len(kk))
+	mLen := fmt.Sprintf("%d", len(m))
+	nilMap := map[string]int(nil)
+	nm := (*map[string]int)(nil)
+	pm := &m
+	mAddr := fmt.Sprintf("%p", pm)
+	pmAddr := fmt.Sprintf("%p", &pm)
+	mt := "map[string]int"
+	mt1 := "string"
+	mt2 := "int"
+	ms := "(len=" + mLen + ") {\n (" + mt1 + ") (len=" + klen + ") " +
+		"\"one\": (" + mt2 + ") 1,\n (" + mt1 + ") (len=" + kkLen +
+		") \"two\": (" + mt2 + ") 2\n}"
+	ms2 := "(len=" + mLen + ") {\n (" + mt1 + ") (len=" + kkLen + ") " +
+		"\"two\": (" + mt2 + ") 2,\n (" + mt1 + ") (len=" + klen +
+		") \"one\": (" + mt2 + ") 1\n}"
+	addDumpTest(m, "("+mt+") "+ms+"\n", "("+mt+") "+ms2+"\n")
+	addDumpTest(pm, "(*"+mt+")("+mAddr+")("+ms+")\n",
+		"(*"+mt+")("+mAddr+")("+ms2+")\n")
+	addDumpTest(&pm, "(**"+mt+")("+pmAddr+"->"+mAddr+")("+ms+")\n",
+		"(**"+mt+")("+pmAddr+"->"+mAddr+")("+ms2+")\n")
+	addDumpTest(nm, "(*"+mt+")(<nil>)\n")
+	addDumpTest(nilMap, "("+mt+") <nil>\n")
+
+	// Map with custom formatter type on pointer receiver only keys and vals.
+	k2 := pstringer("one")
+	v2 := pstringer("1")
+	m2 := map[pstringer]pstringer{k2: v2}
+	k2Len := fmt.Sprintf("%d", len(k2))
+	v2Len := fmt.Sprintf("%d", len(v2))
+	m2Len := fmt.Sprintf("%d", len(m2))
+	nilMap2 := map[pstringer]pstringer(nil)
+	nm2 := (*map[pstringer]pstringer)(nil)
+	pm2 := &m2
+	m2Addr := fmt.Sprintf("%p", pm2)
+	pm2Addr := fmt.Sprintf("%p", &pm2)
+	m2t := "map[spew_test.pstringer]spew_test.pstringer"
+	m2t1 := "spew_test.pstringer"
+	m2t2 := "spew_test.pstringer"
+	m2s := "(len=" + m2Len + ") {\n (" + m2t1 + ") (len=" + k2Len + ") " +
+		"stringer one: (" + m2t2 + ") (len=" + v2Len + ") stringer 1\n}"
+	if spew.UnsafeDisabled {
+		m2s = "(len=" + m2Len + ") {\n (" + m2t1 + ") (len=" + k2Len +
+			") " + "\"one\": (" + m2t2 + ") (len=" + v2Len +
+			") \"1\"\n}"
+	}
+	addDumpTest(m2, "("+m2t+") "+m2s+"\n")
+	addDumpTest(pm2, "(*"+m2t+")("+m2Addr+")("+m2s+")\n")
+	addDumpTest(&pm2, "(**"+m2t+")("+pm2Addr+"->"+m2Addr+")("+m2s+")\n")
+	addDumpTest(nm2, "(*"+m2t+")(<nil>)\n")
+	addDumpTest(nilMap2, "("+m2t+") <nil>\n")
+
+	// Map with interface keys and values.
+	k3 := "one"
+	k3Len := fmt.Sprintf("%d", len(k3))
+	m3 := map[interface{}]interface{}{k3: 1}
+	m3Len := fmt.Sprintf("%d", len(m3))
+	nilMap3 := map[interface{}]interface{}(nil)
+	nm3 := (*map[interface{}]interface{})(nil)
+	pm3 := &m3
+	m3Addr := fmt.Sprintf("%p", pm3)
+	pm3Addr := fmt.Sprintf("%p", &pm3)
+	m3t := "map[interface {}]interface {}"
+	m3t1 := "string"
+	m3t2 := "int"
+	m3s := "(len=" + m3Len + ") {\n (" + m3t1 + ") (len=" + k3Len + ") " +
+		"\"one\": (" + m3t2 + ") 1\n}"
+	addDumpTest(m3, "("+m3t+") "+m3s+"\n")
+	addDumpTest(pm3, "(*"+m3t+")("+m3Addr+")("+m3s+")\n")
+	addDumpTest(&pm3, "(**"+m3t+")("+pm3Addr+"->"+m3Addr+")("+m3s+")\n")
+	addDumpTest(nm3, "(*"+m3t+")(<nil>)\n")
+	addDumpTest(nilMap3, "("+m3t+") <nil>\n")
+
+	// Map with nil interface value.
+	k4 := "nil"
+	k4Len := fmt.Sprintf("%d", len(k4))
+	m4 := map[string]interface{}{k4: nil}
+	m4Len := fmt.Sprintf("%d", len(m4))
+	nilMap4 := map[string]interface{}(nil)
+	nm4 := (*map[string]interface{})(nil)
+	pm4 := &m4
+	m4Addr := fmt.Sprintf("%p", pm4)
+	pm4Addr := fmt.Sprintf("%p", &pm4)
+	m4t := "map[string]interface {}"
+	m4t1 := "string"
+	m4t2 := "interface {}"
+	m4s := "(len=" + m4Len + ") {\n (" + m4t1 + ") (len=" + k4Len + ")" +
+		" \"nil\": (" + m4t2 + ") <nil>\n}"
+	addDumpTest(m4, "("+m4t+") "+m4s+"\n")
+	addDumpTest(pm4, "(*"+m4t+")("+m4Addr+")("+m4s+")\n")
+	addDumpTest(&pm4, "(**"+m4t+")("+pm4Addr+"->"+m4Addr+")("+m4s+")\n")
+	addDumpTest(nm4, "(*"+m4t+")(<nil>)\n")
+	addDumpTest(nilMap4, "("+m4t+") <nil>\n")
+}
+
+func addStructDumpTests() {
+	// Struct with primitives.
+	type s1 struct {
+		a int8
+		b uint8
+	}
+	v := s1{127, 255}
+	nv := (*s1)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "spew_test.s1"
+	vt2 := "int8"
+	vt3 := "uint8"
+	vs := "{\n a: (" + vt2 + ") 127,\n b: (" + vt3 + ") 255\n}"
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+
+	// Struct that contains another struct.
+	type s2 struct {
+		s1 s1
+		b  bool
+	}
+	v2 := s2{s1{127, 255}, true}
+	nv2 := (*s2)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "spew_test.s2"
+	v2t2 := "spew_test.s1"
+	v2t3 := "int8"
+	v2t4 := "uint8"
+	v2t5 := "bool"
+	v2s := "{\n s1: (" + v2t2 + ") {\n  a: (" + v2t3 + ") 127,\n  b: (" +
+		v2t4 + ") 255\n },\n b: (" + v2t5 + ") true\n}"
+	addDumpTest(v2, "("+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n")
+	addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n")
+	addDumpTest(nv2, "(*"+v2t+")(<nil>)\n")
+
+	// Struct that contains custom type with Stringer pointer interface via both
+	// exported and unexported fields.
+	type s3 struct {
+		s pstringer
+		S pstringer
+	}
+	v3 := s3{"test", "test2"}
+	nv3 := (*s3)(nil)
+	pv3 := &v3
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "spew_test.s3"
+	v3t2 := "spew_test.pstringer"
+	v3s := "{\n s: (" + v3t2 + ") (len=4) stringer test,\n S: (" + v3t2 +
+		") (len=5) stringer test2\n}"
+	v3sp := v3s
+	if spew.UnsafeDisabled {
+		v3s = "{\n s: (" + v3t2 + ") (len=4) \"test\",\n S: (" +
+			v3t2 + ") (len=5) \"test2\"\n}"
+		v3sp = "{\n s: (" + v3t2 + ") (len=4) \"test\",\n S: (" +
+			v3t2 + ") (len=5) stringer test2\n}"
+	}
+	addDumpTest(v3, "("+v3t+") "+v3s+"\n")
+	addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3sp+")\n")
+	addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3sp+")\n")
+	addDumpTest(nv3, "(*"+v3t+")(<nil>)\n")
+
+	// Struct that contains embedded struct and field to same struct.
+	e := embed{"embedstr"}
+	eLen := fmt.Sprintf("%d", len("embedstr"))
+	v4 := embedwrap{embed: &e, e: &e}
+	nv4 := (*embedwrap)(nil)
+	pv4 := &v4
+	eAddr := fmt.Sprintf("%p", &e)
+	v4Addr := fmt.Sprintf("%p", pv4)
+	pv4Addr := fmt.Sprintf("%p", &pv4)
+	v4t := "spew_test.embedwrap"
+	v4t2 := "spew_test.embed"
+	v4t3 := "string"
+	v4s := "{\n embed: (*" + v4t2 + ")(" + eAddr + ")({\n  a: (" + v4t3 +
+		") (len=" + eLen + ") \"embedstr\"\n }),\n e: (*" + v4t2 +
+		")(" + eAddr + ")({\n  a: (" + v4t3 + ") (len=" + eLen + ")" +
+		" \"embedstr\"\n })\n}"
+	addDumpTest(v4, "("+v4t+") "+v4s+"\n")
+	addDumpTest(pv4, "(*"+v4t+")("+v4Addr+")("+v4s+")\n")
+	addDumpTest(&pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")("+v4s+")\n")
+	addDumpTest(nv4, "(*"+v4t+")(<nil>)\n")
+}
+
+func addUintptrDumpTests() {
+	// Null pointer.
+	v := uintptr(0)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "uintptr"
+	vs := "<nil>"
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+
+	// Address of real variable.
+	i := 1
+	v2 := uintptr(unsafe.Pointer(&i))
+	nv2 := (*uintptr)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "uintptr"
+	v2s := fmt.Sprintf("%p", &i)
+	addDumpTest(v2, "("+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n")
+	addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n")
+	addDumpTest(nv2, "(*"+v2t+")(<nil>)\n")
+}
+
+func addUnsafePointerDumpTests() {
+	// Null pointer.
+	v := unsafe.Pointer(uintptr(0))
+	nv := (*unsafe.Pointer)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "unsafe.Pointer"
+	vs := "<nil>"
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+
+	// Address of real variable.
+	i := 1
+	v2 := unsafe.Pointer(&i)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "unsafe.Pointer"
+	v2s := fmt.Sprintf("%p", &i)
+	addDumpTest(v2, "("+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n")
+	addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+}
+
+func addChanDumpTests() {
+	// Nil channel.
+	var v chan int
+	pv := &v
+	nv := (*chan int)(nil)
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "chan int"
+	vs := "<nil>"
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+
+	// Real channel.
+	v2 := make(chan int)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "chan int"
+	v2s := fmt.Sprintf("%p", v2)
+	addDumpTest(v2, "("+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n")
+	addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n")
+}
+
+func addFuncDumpTests() {
+	// Function with no params and no returns.
+	v := addIntDumpTests
+	nv := (*func())(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "func()"
+	vs := fmt.Sprintf("%p", v)
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+
+	// Function with param and no returns.
+	v2 := TestDump
+	nv2 := (*func(*testing.T))(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "func(*testing.T)"
+	v2s := fmt.Sprintf("%p", v2)
+	addDumpTest(v2, "("+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n")
+	addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n")
+	addDumpTest(nv2, "(*"+v2t+")(<nil>)\n")
+
+	// Function with multiple params and multiple returns.
+	var v3 = func(i int, s string) (b bool, err error) {
+		return true, nil
+	}
+	nv3 := (*func(int, string) (bool, error))(nil)
+	pv3 := &v3
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "func(int, string) (bool, error)"
+	v3s := fmt.Sprintf("%p", v3)
+	addDumpTest(v3, "("+v3t+") "+v3s+"\n")
+	addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3s+")\n")
+	addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3s+")\n")
+	addDumpTest(nv3, "(*"+v3t+")(<nil>)\n")
+}
+
+func addCircularDumpTests() {
+	// Struct that is circular through self referencing.
+	type circular struct {
+		c *circular
+	}
+	v := circular{nil}
+	v.c = &v
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "spew_test.circular"
+	vs := "{\n c: (*" + vt + ")(" + vAddr + ")({\n  c: (*" + vt + ")(" +
+		vAddr + ")(<already shown>)\n })\n}"
+	vs2 := "{\n c: (*" + vt + ")(" + vAddr + ")(<already shown>)\n}"
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs2+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs2+")\n")
+
+	// Structs that are circular through cross referencing.
+	v2 := xref1{nil}
+	ts2 := xref2{&v2}
+	v2.ps2 = &ts2
+	pv2 := &v2
+	ts2Addr := fmt.Sprintf("%p", &ts2)
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "spew_test.xref1"
+	v2t2 := "spew_test.xref2"
+	v2s := "{\n ps2: (*" + v2t2 + ")(" + ts2Addr + ")({\n  ps1: (*" + v2t +
+		")(" + v2Addr + ")({\n   ps2: (*" + v2t2 + ")(" + ts2Addr +
+		")(<already shown>)\n  })\n })\n}"
+	v2s2 := "{\n ps2: (*" + v2t2 + ")(" + ts2Addr + ")({\n  ps1: (*" + v2t +
+		")(" + v2Addr + ")(<already shown>)\n })\n}"
+	addDumpTest(v2, "("+v2t+") "+v2s+"\n")
+	addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s2+")\n")
+	addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s2+")\n")
+
+	// Structs that are indirectly circular.
+	v3 := indirCir1{nil}
+	tic2 := indirCir2{nil}
+	tic3 := indirCir3{&v3}
+	tic2.ps3 = &tic3
+	v3.ps2 = &tic2
+	pv3 := &v3
+	tic2Addr := fmt.Sprintf("%p", &tic2)
+	tic3Addr := fmt.Sprintf("%p", &tic3)
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "spew_test.indirCir1"
+	v3t2 := "spew_test.indirCir2"
+	v3t3 := "spew_test.indirCir3"
+	v3s := "{\n ps2: (*" + v3t2 + ")(" + tic2Addr + ")({\n  ps3: (*" + v3t3 +
+		")(" + tic3Addr + ")({\n   ps1: (*" + v3t + ")(" + v3Addr +
+		")({\n    ps2: (*" + v3t2 + ")(" + tic2Addr +
+		")(<already shown>)\n   })\n  })\n })\n}"
+	v3s2 := "{\n ps2: (*" + v3t2 + ")(" + tic2Addr + ")({\n  ps3: (*" + v3t3 +
+		")(" + tic3Addr + ")({\n   ps1: (*" + v3t + ")(" + v3Addr +
+		")(<already shown>)\n  })\n })\n}"
+	addDumpTest(v3, "("+v3t+") "+v3s+"\n")
+	addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3s2+")\n")
+	addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3s2+")\n")
+}
+
+func addPanicDumpTests() {
+	// Type that panics in its Stringer interface.
+	v := panicer(127)
+	nv := (*panicer)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "spew_test.panicer"
+	vs := "(PANIC=test panic)127"
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+}
+
+func addErrorDumpTests() {
+	// Type that has a custom Error interface.
+	v := customError(127)
+	nv := (*customError)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "spew_test.customError"
+	vs := "error: 127"
+	addDumpTest(v, "("+vt+") "+vs+"\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n")
+	addDumpTest(nv, "(*"+vt+")(<nil>)\n")
+}
+
+// TestDump executes all of the tests described by dumpTests.
+func TestDump(t *testing.T) {
+	// Setup tests.
+	addIntDumpTests()
+	addUintDumpTests()
+	addBoolDumpTests()
+	addFloatDumpTests()
+	addComplexDumpTests()
+	addArrayDumpTests()
+	addSliceDumpTests()
+	addStringDumpTests()
+	addInterfaceDumpTests()
+	addMapDumpTests()
+	addStructDumpTests()
+	addUintptrDumpTests()
+	addUnsafePointerDumpTests()
+	addChanDumpTests()
+	addFuncDumpTests()
+	addCircularDumpTests()
+	addPanicDumpTests()
+	addErrorDumpTests()
+	addCgoDumpTests()
+
+	t.Logf("Running %d tests", len(dumpTests))
+	for i, test := range dumpTests {
+		buf := new(bytes.Buffer)
+		spew.Fdump(buf, test.in)
+		s := buf.String()
+		if testFailed(s, test.wants) {
+			t.Errorf("Dump #%d\n got: %s %s", i, s, stringizeWants(test.wants))
+			continue
+		}
+	}
+}
+
+func TestDumpSortedKeys(t *testing.T) {
+	cfg := spew.ConfigState{SortKeys: true}
+	s := cfg.Sdump(map[int]string{1: "1", 3: "3", 2: "2"})
+	expected := "(map[int]string) (len=3) {\n(int) 1: (string) (len=1) " +
+		"\"1\",\n(int) 2: (string) (len=1) \"2\",\n(int) 3: (string) " +
+		"(len=1) \"3\"\n" +
+		"}\n"
+	if s != expected {
+		t.Errorf("Sorted keys mismatch:\n  %v %v", s, expected)
+	}
+
+	s = cfg.Sdump(map[stringer]int{"1": 1, "3": 3, "2": 2})
+	expected = "(map[spew_test.stringer]int) (len=3) {\n" +
+		"(spew_test.stringer) (len=1) stringer 1: (int) 1,\n" +
+		"(spew_test.stringer) (len=1) stringer 2: (int) 2,\n" +
+		"(spew_test.stringer) (len=1) stringer 3: (int) 3\n" +
+		"}\n"
+	if s != expected {
+		t.Errorf("Sorted keys mismatch:\n  %v %v", s, expected)
+	}
+
+	s = cfg.Sdump(map[pstringer]int{pstringer("1"): 1, pstringer("3"): 3, pstringer("2"): 2})
+	expected = "(map[spew_test.pstringer]int) (len=3) {\n" +
+		"(spew_test.pstringer) (len=1) stringer 1: (int) 1,\n" +
+		"(spew_test.pstringer) (len=1) stringer 2: (int) 2,\n" +
+		"(spew_test.pstringer) (len=1) stringer 3: (int) 3\n" +
+		"}\n"
+	if spew.UnsafeDisabled {
+		expected = "(map[spew_test.pstringer]int) (len=3) {\n" +
+			"(spew_test.pstringer) (len=1) \"1\": (int) 1,\n" +
+			"(spew_test.pstringer) (len=1) \"2\": (int) 2,\n" +
+			"(spew_test.pstringer) (len=1) \"3\": (int) 3\n" +
+			"}\n"
+	}
+	if s != expected {
+		t.Errorf("Sorted keys mismatch:\n  %v %v", s, expected)
+	}
+
+	s = cfg.Sdump(map[customError]int{customError(1): 1, customError(3): 3, customError(2): 2})
+	expected = "(map[spew_test.customError]int) (len=3) {\n" +
+		"(spew_test.customError) error: 1: (int) 1,\n" +
+		"(spew_test.customError) error: 2: (int) 2,\n" +
+		"(spew_test.customError) error: 3: (int) 3\n" +
+		"}\n"
+	if s != expected {
+		t.Errorf("Sorted keys mismatch:\n  %v %v", s, expected)
+	}
+
+}

+ 99 - 0
vendor/github.com/davecgh/go-spew/spew/dumpcgo_test.go

@@ -0,0 +1,99 @@
+// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+// NOTE: Due to the following build constraints, this file will only be compiled
+// when both cgo is supported and "-tags testcgo" is added to the go test
+// command line.  This means the cgo tests are only added (and hence run) when
+// specifially requested.  This configuration is used because spew itself
+// does not require cgo to run even though it does handle certain cgo types
+// specially.  Rather than forcing all clients to require cgo and an external
+// C compiler just to run the tests, this scheme makes them optional.
+// +build cgo,testcgo
+
+package spew_test
+
+import (
+	"fmt"
+
+	"github.com/davecgh/go-spew/spew/testdata"
+)
+
+func addCgoDumpTests() {
+	// C char pointer.
+	v := testdata.GetCgoCharPointer()
+	nv := testdata.GetCgoNullCharPointer()
+	pv := &v
+	vcAddr := fmt.Sprintf("%p", v)
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "*testdata._Ctype_char"
+	vs := "116"
+	addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n")
+	addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n")
+	addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n")
+	addDumpTest(nv, "("+vt+")(<nil>)\n")
+
+	// C char array.
+	v2, v2l, v2c := testdata.GetCgoCharArray()
+	v2Len := fmt.Sprintf("%d", v2l)
+	v2Cap := fmt.Sprintf("%d", v2c)
+	v2t := "[6]testdata._Ctype_char"
+	v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " +
+		"{\n 00000000  74 65 73 74 32 00                               " +
+		"  |test2.|\n}"
+	addDumpTest(v2, "("+v2t+") "+v2s+"\n")
+
+	// C unsigned char array.
+	v3, v3l, v3c := testdata.GetCgoUnsignedCharArray()
+	v3Len := fmt.Sprintf("%d", v3l)
+	v3Cap := fmt.Sprintf("%d", v3c)
+	v3t := "[6]testdata._Ctype_unsignedchar"
+	v3t2 := "[6]testdata._Ctype_uchar"
+	v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " +
+		"{\n 00000000  74 65 73 74 33 00                               " +
+		"  |test3.|\n}"
+	addDumpTest(v3, "("+v3t+") "+v3s+"\n", "("+v3t2+") "+v3s+"\n")
+
+	// C signed char array.
+	v4, v4l, v4c := testdata.GetCgoSignedCharArray()
+	v4Len := fmt.Sprintf("%d", v4l)
+	v4Cap := fmt.Sprintf("%d", v4c)
+	v4t := "[6]testdata._Ctype_schar"
+	v4t2 := "testdata._Ctype_schar"
+	v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
+		"{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 +
+		") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 +
+		") 0\n}"
+	addDumpTest(v4, "("+v4t+") "+v4s+"\n")
+
+	// C uint8_t array.
+	v5, v5l, v5c := testdata.GetCgoUint8tArray()
+	v5Len := fmt.Sprintf("%d", v5l)
+	v5Cap := fmt.Sprintf("%d", v5c)
+	v5t := "[6]testdata._Ctype_uint8_t"
+	v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " +
+		"{\n 00000000  74 65 73 74 35 00                               " +
+		"  |test5.|\n}"
+	addDumpTest(v5, "("+v5t+") "+v5s+"\n")
+
+	// C typedefed unsigned char array.
+	v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray()
+	v6Len := fmt.Sprintf("%d", v6l)
+	v6Cap := fmt.Sprintf("%d", v6c)
+	v6t := "[6]testdata._Ctype_custom_uchar_t"
+	v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " +
+		"{\n 00000000  74 65 73 74 36 00                               " +
+		"  |test6.|\n}"
+	addDumpTest(v6, "("+v6t+") "+v6s+"\n")
+}

+ 26 - 0
vendor/github.com/davecgh/go-spew/spew/dumpnocgo_test.go

@@ -0,0 +1,26 @@
+// Copyright (c) 2013 Dave Collins <dave@davec.name>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+// NOTE: Due to the following build constraints, this file will only be compiled
+// when either cgo is not supported or "-tags testcgo" is not added to the go
+// test command line.  This file intentionally does not setup any cgo tests in
+// this scenario.
+// +build !cgo !testcgo
+
+package spew_test
+
+func addCgoDumpTests() {
+	// Don't add any tests for cgo since this file is only compiled when
+	// there should not be any cgo tests.
+}

+ 226 - 0
vendor/github.com/davecgh/go-spew/spew/example_test.go

@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package spew_test
+
+import (
+	"fmt"
+
+	"github.com/davecgh/go-spew/spew"
+)
+
+type Flag int
+
+const (
+	flagOne Flag = iota
+	flagTwo
+)
+
+var flagStrings = map[Flag]string{
+	flagOne: "flagOne",
+	flagTwo: "flagTwo",
+}
+
+func (f Flag) String() string {
+	if s, ok := flagStrings[f]; ok {
+		return s
+	}
+	return fmt.Sprintf("Unknown flag (%d)", int(f))
+}
+
+type Bar struct {
+	data uintptr
+}
+
+type Foo struct {
+	unexportedField Bar
+	ExportedField   map[interface{}]interface{}
+}
+
+// This example demonstrates how to use Dump to dump variables to stdout.
+func ExampleDump() {
+	// The following package level declarations are assumed for this example:
+	/*
+		type Flag int
+
+		const (
+			flagOne Flag = iota
+			flagTwo
+		)
+
+		var flagStrings = map[Flag]string{
+			flagOne: "flagOne",
+			flagTwo: "flagTwo",
+		}
+
+		func (f Flag) String() string {
+			if s, ok := flagStrings[f]; ok {
+				return s
+			}
+			return fmt.Sprintf("Unknown flag (%d)", int(f))
+		}
+
+		type Bar struct {
+			data uintptr
+		}
+
+		type Foo struct {
+			unexportedField Bar
+			ExportedField   map[interface{}]interface{}
+		}
+	*/
+
+	// Setup some sample data structures for the example.
+	bar := Bar{uintptr(0)}
+	s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
+	f := Flag(5)
+	b := []byte{
+		0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+		0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+		0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+		0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
+		0x31, 0x32,
+	}
+
+	// Dump!
+	spew.Dump(s1, f, b)
+
+	// Output:
+	// (spew_test.Foo) {
+	//  unexportedField: (spew_test.Bar) {
+	//   data: (uintptr) <nil>
+	//  },
+	//  ExportedField: (map[interface {}]interface {}) (len=1) {
+	//   (string) (len=3) "one": (bool) true
+	//  }
+	// }
+	// (spew_test.Flag) Unknown flag (5)
+	// ([]uint8) (len=34 cap=34) {
+	//  00000000  11 12 13 14 15 16 17 18  19 1a 1b 1c 1d 1e 1f 20  |............... |
+	//  00000010  21 22 23 24 25 26 27 28  29 2a 2b 2c 2d 2e 2f 30  |!"#$%&'()*+,-./0|
+	//  00000020  31 32                                             |12|
+	// }
+	//
+}
+
+// This example demonstrates how to use Printf to display a variable with a
+// format string and inline formatting.
+func ExamplePrintf() {
+	// Create a double pointer to a uint 8.
+	ui8 := uint8(5)
+	pui8 := &ui8
+	ppui8 := &pui8
+
+	// Create a circular data type.
+	type circular struct {
+		ui8 uint8
+		c   *circular
+	}
+	c := circular{ui8: 1}
+	c.c = &c
+
+	// Print!
+	spew.Printf("ppui8: %v\n", ppui8)
+	spew.Printf("circular: %v\n", c)
+
+	// Output:
+	// ppui8: <**>5
+	// circular: {1 <*>{1 <*><shown>}}
+}
+
+// This example demonstrates how to use a ConfigState.
+func ExampleConfigState() {
+	// Modify the indent level of the ConfigState only.  The global
+	// configuration is not modified.
+	scs := spew.ConfigState{Indent: "\t"}
+
+	// Output using the ConfigState instance.
+	v := map[string]int{"one": 1}
+	scs.Printf("v: %v\n", v)
+	scs.Dump(v)
+
+	// Output:
+	// v: map[one:1]
+	// (map[string]int) (len=1) {
+	// 	(string) (len=3) "one": (int) 1
+	// }
+}
+
+// This example demonstrates how to use ConfigState.Dump to dump variables to
+// stdout
+func ExampleConfigState_Dump() {
+	// See the top-level Dump example for details on the types used in this
+	// example.
+
+	// Create two ConfigState instances with different indentation.
+	scs := spew.ConfigState{Indent: "\t"}
+	scs2 := spew.ConfigState{Indent: " "}
+
+	// Setup some sample data structures for the example.
+	bar := Bar{uintptr(0)}
+	s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
+
+	// Dump using the ConfigState instances.
+	scs.Dump(s1)
+	scs2.Dump(s1)
+
+	// Output:
+	// (spew_test.Foo) {
+	// 	unexportedField: (spew_test.Bar) {
+	// 		data: (uintptr) <nil>
+	// 	},
+	// 	ExportedField: (map[interface {}]interface {}) (len=1) {
+	//		(string) (len=3) "one": (bool) true
+	// 	}
+	// }
+	// (spew_test.Foo) {
+	//  unexportedField: (spew_test.Bar) {
+	//   data: (uintptr) <nil>
+	//  },
+	//  ExportedField: (map[interface {}]interface {}) (len=1) {
+	//   (string) (len=3) "one": (bool) true
+	//  }
+	// }
+	//
+}
+
+// This example demonstrates how to use ConfigState.Printf to display a variable
+// with a format string and inline formatting.
+func ExampleConfigState_Printf() {
+	// See the top-level Dump example for details on the types used in this
+	// example.
+
+	// Create two ConfigState instances and modify the method handling of the
+	// first ConfigState only.
+	scs := spew.NewDefaultConfig()
+	scs2 := spew.NewDefaultConfig()
+	scs.DisableMethods = true
+
+	// Alternatively
+	// scs := spew.ConfigState{Indent: " ", DisableMethods: true}
+	// scs2 := spew.ConfigState{Indent: " "}
+
+	// This is of type Flag which implements a Stringer and has raw value 1.
+	f := flagTwo
+
+	// Dump using the ConfigState instances.
+	scs.Printf("f: %v\n", f)
+	scs2.Printf("f: %v\n", f)
+
+	// Output:
+	// f: 1
+	// f: flagTwo
+}

+ 1558 - 0
vendor/github.com/davecgh/go-spew/spew/format_test.go

@@ -0,0 +1,1558 @@
+/*
+ * Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+Test Summary:
+NOTE: For each test, a nil pointer, a single pointer and double pointer to the
+base test element are also tested to ensure proper indirection across all types.
+
+- Max int8, int16, int32, int64, int
+- Max uint8, uint16, uint32, uint64, uint
+- Boolean true and false
+- Standard complex64 and complex128
+- Array containing standard ints
+- Array containing type with custom formatter on pointer receiver only
+- Array containing interfaces
+- Slice containing standard float32 values
+- Slice containing type with custom formatter on pointer receiver only
+- Slice containing interfaces
+- Nil slice
+- Standard string
+- Nil interface
+- Sub-interface
+- Map with string keys and int vals
+- Map with custom formatter type on pointer receiver only keys and vals
+- Map with interface keys and values
+- Map with nil interface value
+- Struct with primitives
+- Struct that contains another struct
+- Struct that contains custom type with Stringer pointer interface via both
+  exported and unexported fields
+- Struct that contains embedded struct and field to same struct
+- Uintptr to 0 (null pointer)
+- Uintptr address of real variable
+- Unsafe.Pointer to 0 (null pointer)
+- Unsafe.Pointer to address of real variable
+- Nil channel
+- Standard int channel
+- Function with no params and no returns
+- Function with param and no returns
+- Function with multiple params and multiple returns
+- Struct that is circular through self referencing
+- Structs that are circular through cross referencing
+- Structs that are indirectly circular
+- Type that panics in its Stringer interface
+- Type that has a custom Error interface
+- %x passthrough with uint
+- %#x passthrough with uint
+- %f passthrough with precision
+- %f passthrough with width and precision
+- %d passthrough with width
+- %q passthrough with string
+*/
+
+package spew_test
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+	"unsafe"
+
+	"github.com/davecgh/go-spew/spew"
+)
+
+// formatterTest is used to describe a test to be performed against NewFormatter.
+type formatterTest struct {
+	format string
+	in     interface{}
+	wants  []string
+}
+
+// formatterTests houses all of the tests to be performed against NewFormatter.
+var formatterTests = make([]formatterTest, 0)
+
+// addFormatterTest is a helper method to append the passed input and desired
+// result to formatterTests.
+func addFormatterTest(format string, in interface{}, wants ...string) {
+	test := formatterTest{format, in, wants}
+	formatterTests = append(formatterTests, test)
+}
+
+func addIntFormatterTests() {
+	// Max int8.
+	v := int8(127)
+	nv := (*int8)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "int8"
+	vs := "127"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Max int16.
+	v2 := int16(32767)
+	nv2 := (*int16)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "int16"
+	v2s := "32767"
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s)
+	addFormatterTest("%v", &pv2, "<**>"+v2s)
+	addFormatterTest("%v", nv2, "<nil>")
+	addFormatterTest("%+v", v2, v2s)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s)
+	addFormatterTest("%#v", nv2, "(*"+v2t+")"+"<nil>")
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"<nil>")
+
+	// Max int32.
+	v3 := int32(2147483647)
+	nv3 := (*int32)(nil)
+	pv3 := &v3
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "int32"
+	v3s := "2147483647"
+	addFormatterTest("%v", v3, v3s)
+	addFormatterTest("%v", pv3, "<*>"+v3s)
+	addFormatterTest("%v", &pv3, "<**>"+v3s)
+	addFormatterTest("%v", nv3, "<nil>")
+	addFormatterTest("%+v", v3, v3s)
+	addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s)
+	addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s)
+	addFormatterTest("%+v", nv3, "<nil>")
+	addFormatterTest("%#v", v3, "("+v3t+")"+v3s)
+	addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s)
+	addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s)
+	addFormatterTest("%#v", nv3, "(*"+v3t+")"+"<nil>")
+	addFormatterTest("%#+v", v3, "("+v3t+")"+v3s)
+	addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s)
+	addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s)
+	addFormatterTest("%#v", nv3, "(*"+v3t+")"+"<nil>")
+
+	// Max int64.
+	v4 := int64(9223372036854775807)
+	nv4 := (*int64)(nil)
+	pv4 := &v4
+	v4Addr := fmt.Sprintf("%p", pv4)
+	pv4Addr := fmt.Sprintf("%p", &pv4)
+	v4t := "int64"
+	v4s := "9223372036854775807"
+	addFormatterTest("%v", v4, v4s)
+	addFormatterTest("%v", pv4, "<*>"+v4s)
+	addFormatterTest("%v", &pv4, "<**>"+v4s)
+	addFormatterTest("%v", nv4, "<nil>")
+	addFormatterTest("%+v", v4, v4s)
+	addFormatterTest("%+v", pv4, "<*>("+v4Addr+")"+v4s)
+	addFormatterTest("%+v", &pv4, "<**>("+pv4Addr+"->"+v4Addr+")"+v4s)
+	addFormatterTest("%+v", nv4, "<nil>")
+	addFormatterTest("%#v", v4, "("+v4t+")"+v4s)
+	addFormatterTest("%#v", pv4, "(*"+v4t+")"+v4s)
+	addFormatterTest("%#v", &pv4, "(**"+v4t+")"+v4s)
+	addFormatterTest("%#v", nv4, "(*"+v4t+")"+"<nil>")
+	addFormatterTest("%#+v", v4, "("+v4t+")"+v4s)
+	addFormatterTest("%#+v", pv4, "(*"+v4t+")("+v4Addr+")"+v4s)
+	addFormatterTest("%#+v", &pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")"+v4s)
+	addFormatterTest("%#+v", nv4, "(*"+v4t+")"+"<nil>")
+
+	// Max int.
+	v5 := int(2147483647)
+	nv5 := (*int)(nil)
+	pv5 := &v5
+	v5Addr := fmt.Sprintf("%p", pv5)
+	pv5Addr := fmt.Sprintf("%p", &pv5)
+	v5t := "int"
+	v5s := "2147483647"
+	addFormatterTest("%v", v5, v5s)
+	addFormatterTest("%v", pv5, "<*>"+v5s)
+	addFormatterTest("%v", &pv5, "<**>"+v5s)
+	addFormatterTest("%v", nv5, "<nil>")
+	addFormatterTest("%+v", v5, v5s)
+	addFormatterTest("%+v", pv5, "<*>("+v5Addr+")"+v5s)
+	addFormatterTest("%+v", &pv5, "<**>("+pv5Addr+"->"+v5Addr+")"+v5s)
+	addFormatterTest("%+v", nv5, "<nil>")
+	addFormatterTest("%#v", v5, "("+v5t+")"+v5s)
+	addFormatterTest("%#v", pv5, "(*"+v5t+")"+v5s)
+	addFormatterTest("%#v", &pv5, "(**"+v5t+")"+v5s)
+	addFormatterTest("%#v", nv5, "(*"+v5t+")"+"<nil>")
+	addFormatterTest("%#+v", v5, "("+v5t+")"+v5s)
+	addFormatterTest("%#+v", pv5, "(*"+v5t+")("+v5Addr+")"+v5s)
+	addFormatterTest("%#+v", &pv5, "(**"+v5t+")("+pv5Addr+"->"+v5Addr+")"+v5s)
+	addFormatterTest("%#+v", nv5, "(*"+v5t+")"+"<nil>")
+}
+
+func addUintFormatterTests() {
+	// Max uint8.
+	v := uint8(255)
+	nv := (*uint8)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "uint8"
+	vs := "255"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Max uint16.
+	v2 := uint16(65535)
+	nv2 := (*uint16)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "uint16"
+	v2s := "65535"
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s)
+	addFormatterTest("%v", &pv2, "<**>"+v2s)
+	addFormatterTest("%v", nv2, "<nil>")
+	addFormatterTest("%+v", v2, v2s)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s)
+	addFormatterTest("%#v", nv2, "(*"+v2t+")"+"<nil>")
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"<nil>")
+
+	// Max uint32.
+	v3 := uint32(4294967295)
+	nv3 := (*uint32)(nil)
+	pv3 := &v3
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "uint32"
+	v3s := "4294967295"
+	addFormatterTest("%v", v3, v3s)
+	addFormatterTest("%v", pv3, "<*>"+v3s)
+	addFormatterTest("%v", &pv3, "<**>"+v3s)
+	addFormatterTest("%v", nv3, "<nil>")
+	addFormatterTest("%+v", v3, v3s)
+	addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s)
+	addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s)
+	addFormatterTest("%+v", nv3, "<nil>")
+	addFormatterTest("%#v", v3, "("+v3t+")"+v3s)
+	addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s)
+	addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s)
+	addFormatterTest("%#v", nv3, "(*"+v3t+")"+"<nil>")
+	addFormatterTest("%#+v", v3, "("+v3t+")"+v3s)
+	addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s)
+	addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s)
+	addFormatterTest("%#v", nv3, "(*"+v3t+")"+"<nil>")
+
+	// Max uint64.
+	v4 := uint64(18446744073709551615)
+	nv4 := (*uint64)(nil)
+	pv4 := &v4
+	v4Addr := fmt.Sprintf("%p", pv4)
+	pv4Addr := fmt.Sprintf("%p", &pv4)
+	v4t := "uint64"
+	v4s := "18446744073709551615"
+	addFormatterTest("%v", v4, v4s)
+	addFormatterTest("%v", pv4, "<*>"+v4s)
+	addFormatterTest("%v", &pv4, "<**>"+v4s)
+	addFormatterTest("%v", nv4, "<nil>")
+	addFormatterTest("%+v", v4, v4s)
+	addFormatterTest("%+v", pv4, "<*>("+v4Addr+")"+v4s)
+	addFormatterTest("%+v", &pv4, "<**>("+pv4Addr+"->"+v4Addr+")"+v4s)
+	addFormatterTest("%+v", nv4, "<nil>")
+	addFormatterTest("%#v", v4, "("+v4t+")"+v4s)
+	addFormatterTest("%#v", pv4, "(*"+v4t+")"+v4s)
+	addFormatterTest("%#v", &pv4, "(**"+v4t+")"+v4s)
+	addFormatterTest("%#v", nv4, "(*"+v4t+")"+"<nil>")
+	addFormatterTest("%#+v", v4, "("+v4t+")"+v4s)
+	addFormatterTest("%#+v", pv4, "(*"+v4t+")("+v4Addr+")"+v4s)
+	addFormatterTest("%#+v", &pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")"+v4s)
+	addFormatterTest("%#+v", nv4, "(*"+v4t+")"+"<nil>")
+
+	// Max uint.
+	v5 := uint(4294967295)
+	nv5 := (*uint)(nil)
+	pv5 := &v5
+	v5Addr := fmt.Sprintf("%p", pv5)
+	pv5Addr := fmt.Sprintf("%p", &pv5)
+	v5t := "uint"
+	v5s := "4294967295"
+	addFormatterTest("%v", v5, v5s)
+	addFormatterTest("%v", pv5, "<*>"+v5s)
+	addFormatterTest("%v", &pv5, "<**>"+v5s)
+	addFormatterTest("%v", nv5, "<nil>")
+	addFormatterTest("%+v", v5, v5s)
+	addFormatterTest("%+v", pv5, "<*>("+v5Addr+")"+v5s)
+	addFormatterTest("%+v", &pv5, "<**>("+pv5Addr+"->"+v5Addr+")"+v5s)
+	addFormatterTest("%+v", nv5, "<nil>")
+	addFormatterTest("%#v", v5, "("+v5t+")"+v5s)
+	addFormatterTest("%#v", pv5, "(*"+v5t+")"+v5s)
+	addFormatterTest("%#v", &pv5, "(**"+v5t+")"+v5s)
+	addFormatterTest("%#v", nv5, "(*"+v5t+")"+"<nil>")
+	addFormatterTest("%#+v", v5, "("+v5t+")"+v5s)
+	addFormatterTest("%#+v", pv5, "(*"+v5t+")("+v5Addr+")"+v5s)
+	addFormatterTest("%#+v", &pv5, "(**"+v5t+")("+pv5Addr+"->"+v5Addr+")"+v5s)
+	addFormatterTest("%#v", nv5, "(*"+v5t+")"+"<nil>")
+}
+
+func addBoolFormatterTests() {
+	// Boolean true.
+	v := bool(true)
+	nv := (*bool)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "bool"
+	vs := "true"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Boolean false.
+	v2 := bool(false)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "bool"
+	v2s := "false"
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s)
+	addFormatterTest("%v", &pv2, "<**>"+v2s)
+	addFormatterTest("%+v", v2, v2s)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s)
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s)
+}
+
+func addFloatFormatterTests() {
+	// Standard float32.
+	v := float32(3.1415)
+	nv := (*float32)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "float32"
+	vs := "3.1415"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Standard float64.
+	v2 := float64(3.1415926)
+	nv2 := (*float64)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "float64"
+	v2s := "3.1415926"
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s)
+	addFormatterTest("%v", &pv2, "<**>"+v2s)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%+v", v2, v2s)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s)
+	addFormatterTest("%#v", nv2, "(*"+v2t+")"+"<nil>")
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"<nil>")
+}
+
+func addComplexFormatterTests() {
+	// Standard complex64.
+	v := complex(float32(6), -2)
+	nv := (*complex64)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "complex64"
+	vs := "(6-2i)"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Standard complex128.
+	v2 := complex(float64(-6), 2)
+	nv2 := (*complex128)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "complex128"
+	v2s := "(-6+2i)"
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s)
+	addFormatterTest("%v", &pv2, "<**>"+v2s)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%+v", v2, v2s)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s)
+	addFormatterTest("%#v", nv2, "(*"+v2t+")"+"<nil>")
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"<nil>")
+}
+
+func addArrayFormatterTests() {
+	// Array containing standard ints.
+	v := [3]int{1, 2, 3}
+	nv := (*[3]int)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "[3]int"
+	vs := "[1 2 3]"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Array containing type with custom formatter on pointer receiver only.
+	v2 := [3]pstringer{"1", "2", "3"}
+	nv2 := (*[3]pstringer)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "[3]spew_test.pstringer"
+	v2sp := "[stringer 1 stringer 2 stringer 3]"
+	v2s := v2sp
+	if spew.UnsafeDisabled {
+		v2s = "[1 2 3]"
+	}
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2sp)
+	addFormatterTest("%v", &pv2, "<**>"+v2sp)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%+v", v2, v2s)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2sp)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2sp)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2sp)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2sp)
+	addFormatterTest("%#v", nv2, "(*"+v2t+")"+"<nil>")
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2sp)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2sp)
+	addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"<nil>")
+
+	// Array containing interfaces.
+	v3 := [3]interface{}{"one", int(2), uint(3)}
+	nv3 := (*[3]interface{})(nil)
+	pv3 := &v3
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "[3]interface {}"
+	v3t2 := "string"
+	v3t3 := "int"
+	v3t4 := "uint"
+	v3s := "[one 2 3]"
+	v3s2 := "[(" + v3t2 + ")one (" + v3t3 + ")2 (" + v3t4 + ")3]"
+	addFormatterTest("%v", v3, v3s)
+	addFormatterTest("%v", pv3, "<*>"+v3s)
+	addFormatterTest("%v", &pv3, "<**>"+v3s)
+	addFormatterTest("%+v", nv3, "<nil>")
+	addFormatterTest("%+v", v3, v3s)
+	addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s)
+	addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s)
+	addFormatterTest("%+v", nv3, "<nil>")
+	addFormatterTest("%#v", v3, "("+v3t+")"+v3s2)
+	addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s2)
+	addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s2)
+	addFormatterTest("%#v", nv3, "(*"+v3t+")"+"<nil>")
+	addFormatterTest("%#+v", v3, "("+v3t+")"+v3s2)
+	addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s2)
+	addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s2)
+	addFormatterTest("%#+v", nv3, "(*"+v3t+")"+"<nil>")
+}
+
+func addSliceFormatterTests() {
+	// Slice containing standard float32 values.
+	v := []float32{3.14, 6.28, 12.56}
+	nv := (*[]float32)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "[]float32"
+	vs := "[3.14 6.28 12.56]"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Slice containing type with custom formatter on pointer receiver only.
+	v2 := []pstringer{"1", "2", "3"}
+	nv2 := (*[]pstringer)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "[]spew_test.pstringer"
+	v2s := "[stringer 1 stringer 2 stringer 3]"
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s)
+	addFormatterTest("%v", &pv2, "<**>"+v2s)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%+v", v2, v2s)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s)
+	addFormatterTest("%#v", nv2, "(*"+v2t+")"+"<nil>")
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"<nil>")
+
+	// Slice containing interfaces.
+	v3 := []interface{}{"one", int(2), uint(3), nil}
+	nv3 := (*[]interface{})(nil)
+	pv3 := &v3
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "[]interface {}"
+	v3t2 := "string"
+	v3t3 := "int"
+	v3t4 := "uint"
+	v3t5 := "interface {}"
+	v3s := "[one 2 3 <nil>]"
+	v3s2 := "[(" + v3t2 + ")one (" + v3t3 + ")2 (" + v3t4 + ")3 (" + v3t5 +
+		")<nil>]"
+	addFormatterTest("%v", v3, v3s)
+	addFormatterTest("%v", pv3, "<*>"+v3s)
+	addFormatterTest("%v", &pv3, "<**>"+v3s)
+	addFormatterTest("%+v", nv3, "<nil>")
+	addFormatterTest("%+v", v3, v3s)
+	addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s)
+	addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s)
+	addFormatterTest("%+v", nv3, "<nil>")
+	addFormatterTest("%#v", v3, "("+v3t+")"+v3s2)
+	addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s2)
+	addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s2)
+	addFormatterTest("%#v", nv3, "(*"+v3t+")"+"<nil>")
+	addFormatterTest("%#+v", v3, "("+v3t+")"+v3s2)
+	addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s2)
+	addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s2)
+	addFormatterTest("%#+v", nv3, "(*"+v3t+")"+"<nil>")
+
+	// Nil slice.
+	var v4 []int
+	nv4 := (*[]int)(nil)
+	pv4 := &v4
+	v4Addr := fmt.Sprintf("%p", pv4)
+	pv4Addr := fmt.Sprintf("%p", &pv4)
+	v4t := "[]int"
+	v4s := "<nil>"
+	addFormatterTest("%v", v4, v4s)
+	addFormatterTest("%v", pv4, "<*>"+v4s)
+	addFormatterTest("%v", &pv4, "<**>"+v4s)
+	addFormatterTest("%+v", nv4, "<nil>")
+	addFormatterTest("%+v", v4, v4s)
+	addFormatterTest("%+v", pv4, "<*>("+v4Addr+")"+v4s)
+	addFormatterTest("%+v", &pv4, "<**>("+pv4Addr+"->"+v4Addr+")"+v4s)
+	addFormatterTest("%+v", nv4, "<nil>")
+	addFormatterTest("%#v", v4, "("+v4t+")"+v4s)
+	addFormatterTest("%#v", pv4, "(*"+v4t+")"+v4s)
+	addFormatterTest("%#v", &pv4, "(**"+v4t+")"+v4s)
+	addFormatterTest("%#v", nv4, "(*"+v4t+")"+"<nil>")
+	addFormatterTest("%#+v", v4, "("+v4t+")"+v4s)
+	addFormatterTest("%#+v", pv4, "(*"+v4t+")("+v4Addr+")"+v4s)
+	addFormatterTest("%#+v", &pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")"+v4s)
+	addFormatterTest("%#+v", nv4, "(*"+v4t+")"+"<nil>")
+}
+
+func addStringFormatterTests() {
+	// Standard string.
+	v := "test"
+	nv := (*string)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "string"
+	vs := "test"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+}
+
+func addInterfaceFormatterTests() {
+	// Nil interface.
+	var v interface{}
+	nv := (*interface{})(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "interface {}"
+	vs := "<nil>"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Sub-interface.
+	v2 := interface{}(uint16(65535))
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "uint16"
+	v2s := "65535"
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s)
+	addFormatterTest("%v", &pv2, "<**>"+v2s)
+	addFormatterTest("%+v", v2, v2s)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s)
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s)
+}
+
+func addMapFormatterTests() {
+	// Map with string keys and int vals.
+	v := map[string]int{"one": 1, "two": 2}
+	nilMap := map[string]int(nil)
+	nv := (*map[string]int)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "map[string]int"
+	vs := "map[one:1 two:2]"
+	vs2 := "map[two:2 one:1]"
+	addFormatterTest("%v", v, vs, vs2)
+	addFormatterTest("%v", pv, "<*>"+vs, "<*>"+vs2)
+	addFormatterTest("%v", &pv, "<**>"+vs, "<**>"+vs2)
+	addFormatterTest("%+v", nilMap, "<nil>")
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs, vs2)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs, "<*>("+vAddr+")"+vs2)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs,
+		"<**>("+pvAddr+"->"+vAddr+")"+vs2)
+	addFormatterTest("%+v", nilMap, "<nil>")
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs, "("+vt+")"+vs2)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs, "(*"+vt+")"+vs2)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs, "(**"+vt+")"+vs2)
+	addFormatterTest("%#v", nilMap, "("+vt+")"+"<nil>")
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs, "("+vt+")"+vs2)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs,
+		"(*"+vt+")("+vAddr+")"+vs2)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs,
+		"(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs2)
+	addFormatterTest("%#+v", nilMap, "("+vt+")"+"<nil>")
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Map with custom formatter type on pointer receiver only keys and vals.
+	v2 := map[pstringer]pstringer{"one": "1"}
+	nv2 := (*map[pstringer]pstringer)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "map[spew_test.pstringer]spew_test.pstringer"
+	v2s := "map[stringer one:stringer 1]"
+	if spew.UnsafeDisabled {
+		v2s = "map[one:1]"
+	}
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s)
+	addFormatterTest("%v", &pv2, "<**>"+v2s)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%+v", v2, v2s)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s)
+	addFormatterTest("%#v", nv2, "(*"+v2t+")"+"<nil>")
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"<nil>")
+
+	// Map with interface keys and values.
+	v3 := map[interface{}]interface{}{"one": 1}
+	nv3 := (*map[interface{}]interface{})(nil)
+	pv3 := &v3
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "map[interface {}]interface {}"
+	v3t1 := "string"
+	v3t2 := "int"
+	v3s := "map[one:1]"
+	v3s2 := "map[(" + v3t1 + ")one:(" + v3t2 + ")1]"
+	addFormatterTest("%v", v3, v3s)
+	addFormatterTest("%v", pv3, "<*>"+v3s)
+	addFormatterTest("%v", &pv3, "<**>"+v3s)
+	addFormatterTest("%+v", nv3, "<nil>")
+	addFormatterTest("%+v", v3, v3s)
+	addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s)
+	addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s)
+	addFormatterTest("%+v", nv3, "<nil>")
+	addFormatterTest("%#v", v3, "("+v3t+")"+v3s2)
+	addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s2)
+	addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s2)
+	addFormatterTest("%#v", nv3, "(*"+v3t+")"+"<nil>")
+	addFormatterTest("%#+v", v3, "("+v3t+")"+v3s2)
+	addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s2)
+	addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s2)
+	addFormatterTest("%#+v", nv3, "(*"+v3t+")"+"<nil>")
+
+	// Map with nil interface value
+	v4 := map[string]interface{}{"nil": nil}
+	nv4 := (*map[string]interface{})(nil)
+	pv4 := &v4
+	v4Addr := fmt.Sprintf("%p", pv4)
+	pv4Addr := fmt.Sprintf("%p", &pv4)
+	v4t := "map[string]interface {}"
+	v4t1 := "interface {}"
+	v4s := "map[nil:<nil>]"
+	v4s2 := "map[nil:(" + v4t1 + ")<nil>]"
+	addFormatterTest("%v", v4, v4s)
+	addFormatterTest("%v", pv4, "<*>"+v4s)
+	addFormatterTest("%v", &pv4, "<**>"+v4s)
+	addFormatterTest("%+v", nv4, "<nil>")
+	addFormatterTest("%+v", v4, v4s)
+	addFormatterTest("%+v", pv4, "<*>("+v4Addr+")"+v4s)
+	addFormatterTest("%+v", &pv4, "<**>("+pv4Addr+"->"+v4Addr+")"+v4s)
+	addFormatterTest("%+v", nv4, "<nil>")
+	addFormatterTest("%#v", v4, "("+v4t+")"+v4s2)
+	addFormatterTest("%#v", pv4, "(*"+v4t+")"+v4s2)
+	addFormatterTest("%#v", &pv4, "(**"+v4t+")"+v4s2)
+	addFormatterTest("%#v", nv4, "(*"+v4t+")"+"<nil>")
+	addFormatterTest("%#+v", v4, "("+v4t+")"+v4s2)
+	addFormatterTest("%#+v", pv4, "(*"+v4t+")("+v4Addr+")"+v4s2)
+	addFormatterTest("%#+v", &pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")"+v4s2)
+	addFormatterTest("%#+v", nv4, "(*"+v4t+")"+"<nil>")
+}
+
+func addStructFormatterTests() {
+	// Struct with primitives.
+	type s1 struct {
+		a int8
+		b uint8
+	}
+	v := s1{127, 255}
+	nv := (*s1)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "spew_test.s1"
+	vt2 := "int8"
+	vt3 := "uint8"
+	vs := "{127 255}"
+	vs2 := "{a:127 b:255}"
+	vs3 := "{a:(" + vt2 + ")127 b:(" + vt3 + ")255}"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs2)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs2)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs2)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs3)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs3)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs3)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs3)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs3)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs3)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Struct that contains another struct.
+	type s2 struct {
+		s1 s1
+		b  bool
+	}
+	v2 := s2{s1{127, 255}, true}
+	nv2 := (*s2)(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "spew_test.s2"
+	v2t2 := "spew_test.s1"
+	v2t3 := "int8"
+	v2t4 := "uint8"
+	v2t5 := "bool"
+	v2s := "{{127 255} true}"
+	v2s2 := "{s1:{a:127 b:255} b:true}"
+	v2s3 := "{s1:(" + v2t2 + "){a:(" + v2t3 + ")127 b:(" + v2t4 + ")255} b:(" +
+		v2t5 + ")true}"
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s)
+	addFormatterTest("%v", &pv2, "<**>"+v2s)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%+v", v2, v2s2)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s2)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s2)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s3)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s3)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s3)
+	addFormatterTest("%#v", nv2, "(*"+v2t+")"+"<nil>")
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s3)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s3)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s3)
+	addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"<nil>")
+
+	// Struct that contains custom type with Stringer pointer interface via both
+	// exported and unexported fields.
+	type s3 struct {
+		s pstringer
+		S pstringer
+	}
+	v3 := s3{"test", "test2"}
+	nv3 := (*s3)(nil)
+	pv3 := &v3
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "spew_test.s3"
+	v3t2 := "spew_test.pstringer"
+	v3s := "{stringer test stringer test2}"
+	v3sp := v3s
+	v3s2 := "{s:stringer test S:stringer test2}"
+	v3s2p := v3s2
+	v3s3 := "{s:(" + v3t2 + ")stringer test S:(" + v3t2 + ")stringer test2}"
+	v3s3p := v3s3
+	if spew.UnsafeDisabled {
+		v3s = "{test test2}"
+		v3sp = "{test stringer test2}"
+		v3s2 = "{s:test S:test2}"
+		v3s2p = "{s:test S:stringer test2}"
+		v3s3 = "{s:(" + v3t2 + ")test S:(" + v3t2 + ")test2}"
+		v3s3p = "{s:(" + v3t2 + ")test S:(" + v3t2 + ")stringer test2}"
+	}
+	addFormatterTest("%v", v3, v3s)
+	addFormatterTest("%v", pv3, "<*>"+v3sp)
+	addFormatterTest("%v", &pv3, "<**>"+v3sp)
+	addFormatterTest("%+v", nv3, "<nil>")
+	addFormatterTest("%+v", v3, v3s2)
+	addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s2p)
+	addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s2p)
+	addFormatterTest("%+v", nv3, "<nil>")
+	addFormatterTest("%#v", v3, "("+v3t+")"+v3s3)
+	addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s3p)
+	addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s3p)
+	addFormatterTest("%#v", nv3, "(*"+v3t+")"+"<nil>")
+	addFormatterTest("%#+v", v3, "("+v3t+")"+v3s3)
+	addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s3p)
+	addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s3p)
+	addFormatterTest("%#+v", nv3, "(*"+v3t+")"+"<nil>")
+
+	// Struct that contains embedded struct and field to same struct.
+	e := embed{"embedstr"}
+	v4 := embedwrap{embed: &e, e: &e}
+	nv4 := (*embedwrap)(nil)
+	pv4 := &v4
+	eAddr := fmt.Sprintf("%p", &e)
+	v4Addr := fmt.Sprintf("%p", pv4)
+	pv4Addr := fmt.Sprintf("%p", &pv4)
+	v4t := "spew_test.embedwrap"
+	v4t2 := "spew_test.embed"
+	v4t3 := "string"
+	v4s := "{<*>{embedstr} <*>{embedstr}}"
+	v4s2 := "{embed:<*>(" + eAddr + "){a:embedstr} e:<*>(" + eAddr +
+		"){a:embedstr}}"
+	v4s3 := "{embed:(*" + v4t2 + "){a:(" + v4t3 + ")embedstr} e:(*" + v4t2 +
+		"){a:(" + v4t3 + ")embedstr}}"
+	v4s4 := "{embed:(*" + v4t2 + ")(" + eAddr + "){a:(" + v4t3 +
+		")embedstr} e:(*" + v4t2 + ")(" + eAddr + "){a:(" + v4t3 + ")embedstr}}"
+	addFormatterTest("%v", v4, v4s)
+	addFormatterTest("%v", pv4, "<*>"+v4s)
+	addFormatterTest("%v", &pv4, "<**>"+v4s)
+	addFormatterTest("%+v", nv4, "<nil>")
+	addFormatterTest("%+v", v4, v4s2)
+	addFormatterTest("%+v", pv4, "<*>("+v4Addr+")"+v4s2)
+	addFormatterTest("%+v", &pv4, "<**>("+pv4Addr+"->"+v4Addr+")"+v4s2)
+	addFormatterTest("%+v", nv4, "<nil>")
+	addFormatterTest("%#v", v4, "("+v4t+")"+v4s3)
+	addFormatterTest("%#v", pv4, "(*"+v4t+")"+v4s3)
+	addFormatterTest("%#v", &pv4, "(**"+v4t+")"+v4s3)
+	addFormatterTest("%#v", nv4, "(*"+v4t+")"+"<nil>")
+	addFormatterTest("%#+v", v4, "("+v4t+")"+v4s4)
+	addFormatterTest("%#+v", pv4, "(*"+v4t+")("+v4Addr+")"+v4s4)
+	addFormatterTest("%#+v", &pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")"+v4s4)
+	addFormatterTest("%#+v", nv4, "(*"+v4t+")"+"<nil>")
+}
+
+func addUintptrFormatterTests() {
+	// Null pointer.
+	v := uintptr(0)
+	nv := (*uintptr)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "uintptr"
+	vs := "<nil>"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Address of real variable.
+	i := 1
+	v2 := uintptr(unsafe.Pointer(&i))
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "uintptr"
+	v2s := fmt.Sprintf("%p", &i)
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s)
+	addFormatterTest("%v", &pv2, "<**>"+v2s)
+	addFormatterTest("%+v", v2, v2s)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s)
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s)
+}
+
+func addUnsafePointerFormatterTests() {
+	// Null pointer.
+	v := unsafe.Pointer(uintptr(0))
+	nv := (*unsafe.Pointer)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "unsafe.Pointer"
+	vs := "<nil>"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Address of real variable.
+	i := 1
+	v2 := unsafe.Pointer(&i)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "unsafe.Pointer"
+	v2s := fmt.Sprintf("%p", &i)
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s)
+	addFormatterTest("%v", &pv2, "<**>"+v2s)
+	addFormatterTest("%+v", v2, v2s)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s)
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s)
+}
+
+func addChanFormatterTests() {
+	// Nil channel.
+	var v chan int
+	pv := &v
+	nv := (*chan int)(nil)
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "chan int"
+	vs := "<nil>"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Real channel.
+	v2 := make(chan int)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "chan int"
+	v2s := fmt.Sprintf("%p", v2)
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s)
+	addFormatterTest("%v", &pv2, "<**>"+v2s)
+	addFormatterTest("%+v", v2, v2s)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s)
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s)
+}
+
+func addFuncFormatterTests() {
+	// Function with no params and no returns.
+	v := addIntFormatterTests
+	nv := (*func())(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "func()"
+	vs := fmt.Sprintf("%p", v)
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+
+	// Function with param and no returns.
+	v2 := TestFormatter
+	nv2 := (*func(*testing.T))(nil)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "func(*testing.T)"
+	v2s := fmt.Sprintf("%p", v2)
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s)
+	addFormatterTest("%v", &pv2, "<**>"+v2s)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%+v", v2, v2s)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%+v", nv2, "<nil>")
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s)
+	addFormatterTest("%#v", nv2, "(*"+v2t+")"+"<nil>")
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s)
+	addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"<nil>")
+
+	// Function with multiple params and multiple returns.
+	var v3 = func(i int, s string) (b bool, err error) {
+		return true, nil
+	}
+	nv3 := (*func(int, string) (bool, error))(nil)
+	pv3 := &v3
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "func(int, string) (bool, error)"
+	v3s := fmt.Sprintf("%p", v3)
+	addFormatterTest("%v", v3, v3s)
+	addFormatterTest("%v", pv3, "<*>"+v3s)
+	addFormatterTest("%v", &pv3, "<**>"+v3s)
+	addFormatterTest("%+v", nv3, "<nil>")
+	addFormatterTest("%+v", v3, v3s)
+	addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s)
+	addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s)
+	addFormatterTest("%+v", nv3, "<nil>")
+	addFormatterTest("%#v", v3, "("+v3t+")"+v3s)
+	addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s)
+	addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s)
+	addFormatterTest("%#v", nv3, "(*"+v3t+")"+"<nil>")
+	addFormatterTest("%#+v", v3, "("+v3t+")"+v3s)
+	addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s)
+	addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s)
+	addFormatterTest("%#+v", nv3, "(*"+v3t+")"+"<nil>")
+}
+
+func addCircularFormatterTests() {
+	// Struct that is circular through self referencing.
+	type circular struct {
+		c *circular
+	}
+	v := circular{nil}
+	v.c = &v
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "spew_test.circular"
+	vs := "{<*>{<*><shown>}}"
+	vs2 := "{<*><shown>}"
+	vs3 := "{c:<*>(" + vAddr + "){c:<*>(" + vAddr + ")<shown>}}"
+	vs4 := "{c:<*>(" + vAddr + ")<shown>}"
+	vs5 := "{c:(*" + vt + "){c:(*" + vt + ")<shown>}}"
+	vs6 := "{c:(*" + vt + ")<shown>}"
+	vs7 := "{c:(*" + vt + ")(" + vAddr + "){c:(*" + vt + ")(" + vAddr +
+		")<shown>}}"
+	vs8 := "{c:(*" + vt + ")(" + vAddr + ")<shown>}"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs2)
+	addFormatterTest("%v", &pv, "<**>"+vs2)
+	addFormatterTest("%+v", v, vs3)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs4)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs4)
+	addFormatterTest("%#v", v, "("+vt+")"+vs5)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs6)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs6)
+	addFormatterTest("%#+v", v, "("+vt+")"+vs7)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs8)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs8)
+
+	// Structs that are circular through cross referencing.
+	v2 := xref1{nil}
+	ts2 := xref2{&v2}
+	v2.ps2 = &ts2
+	pv2 := &v2
+	ts2Addr := fmt.Sprintf("%p", &ts2)
+	v2Addr := fmt.Sprintf("%p", pv2)
+	pv2Addr := fmt.Sprintf("%p", &pv2)
+	v2t := "spew_test.xref1"
+	v2t2 := "spew_test.xref2"
+	v2s := "{<*>{<*>{<*><shown>}}}"
+	v2s2 := "{<*>{<*><shown>}}"
+	v2s3 := "{ps2:<*>(" + ts2Addr + "){ps1:<*>(" + v2Addr + "){ps2:<*>(" +
+		ts2Addr + ")<shown>}}}"
+	v2s4 := "{ps2:<*>(" + ts2Addr + "){ps1:<*>(" + v2Addr + ")<shown>}}"
+	v2s5 := "{ps2:(*" + v2t2 + "){ps1:(*" + v2t + "){ps2:(*" + v2t2 +
+		")<shown>}}}"
+	v2s6 := "{ps2:(*" + v2t2 + "){ps1:(*" + v2t + ")<shown>}}"
+	v2s7 := "{ps2:(*" + v2t2 + ")(" + ts2Addr + "){ps1:(*" + v2t +
+		")(" + v2Addr + "){ps2:(*" + v2t2 + ")(" + ts2Addr +
+		")<shown>}}}"
+	v2s8 := "{ps2:(*" + v2t2 + ")(" + ts2Addr + "){ps1:(*" + v2t +
+		")(" + v2Addr + ")<shown>}}"
+	addFormatterTest("%v", v2, v2s)
+	addFormatterTest("%v", pv2, "<*>"+v2s2)
+	addFormatterTest("%v", &pv2, "<**>"+v2s2)
+	addFormatterTest("%+v", v2, v2s3)
+	addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s4)
+	addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s4)
+	addFormatterTest("%#v", v2, "("+v2t+")"+v2s5)
+	addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s6)
+	addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s6)
+	addFormatterTest("%#+v", v2, "("+v2t+")"+v2s7)
+	addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s8)
+	addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s8)
+
+	// Structs that are indirectly circular.
+	v3 := indirCir1{nil}
+	tic2 := indirCir2{nil}
+	tic3 := indirCir3{&v3}
+	tic2.ps3 = &tic3
+	v3.ps2 = &tic2
+	pv3 := &v3
+	tic2Addr := fmt.Sprintf("%p", &tic2)
+	tic3Addr := fmt.Sprintf("%p", &tic3)
+	v3Addr := fmt.Sprintf("%p", pv3)
+	pv3Addr := fmt.Sprintf("%p", &pv3)
+	v3t := "spew_test.indirCir1"
+	v3t2 := "spew_test.indirCir2"
+	v3t3 := "spew_test.indirCir3"
+	v3s := "{<*>{<*>{<*>{<*><shown>}}}}"
+	v3s2 := "{<*>{<*>{<*><shown>}}}"
+	v3s3 := "{ps2:<*>(" + tic2Addr + "){ps3:<*>(" + tic3Addr + "){ps1:<*>(" +
+		v3Addr + "){ps2:<*>(" + tic2Addr + ")<shown>}}}}"
+	v3s4 := "{ps2:<*>(" + tic2Addr + "){ps3:<*>(" + tic3Addr + "){ps1:<*>(" +
+		v3Addr + ")<shown>}}}"
+	v3s5 := "{ps2:(*" + v3t2 + "){ps3:(*" + v3t3 + "){ps1:(*" + v3t +
+		"){ps2:(*" + v3t2 + ")<shown>}}}}"
+	v3s6 := "{ps2:(*" + v3t2 + "){ps3:(*" + v3t3 + "){ps1:(*" + v3t +
+		")<shown>}}}"
+	v3s7 := "{ps2:(*" + v3t2 + ")(" + tic2Addr + "){ps3:(*" + v3t3 + ")(" +
+		tic3Addr + "){ps1:(*" + v3t + ")(" + v3Addr + "){ps2:(*" + v3t2 +
+		")(" + tic2Addr + ")<shown>}}}}"
+	v3s8 := "{ps2:(*" + v3t2 + ")(" + tic2Addr + "){ps3:(*" + v3t3 + ")(" +
+		tic3Addr + "){ps1:(*" + v3t + ")(" + v3Addr + ")<shown>}}}"
+	addFormatterTest("%v", v3, v3s)
+	addFormatterTest("%v", pv3, "<*>"+v3s2)
+	addFormatterTest("%v", &pv3, "<**>"+v3s2)
+	addFormatterTest("%+v", v3, v3s3)
+	addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s4)
+	addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s4)
+	addFormatterTest("%#v", v3, "("+v3t+")"+v3s5)
+	addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s6)
+	addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s6)
+	addFormatterTest("%#+v", v3, "("+v3t+")"+v3s7)
+	addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s8)
+	addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s8)
+}
+
+func addPanicFormatterTests() {
+	// Type that panics in its Stringer interface.
+	v := panicer(127)
+	nv := (*panicer)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "spew_test.panicer"
+	vs := "(PANIC=test panic)127"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+}
+
+func addErrorFormatterTests() {
+	// Type that has a custom Error interface.
+	v := customError(127)
+	nv := (*customError)(nil)
+	pv := &v
+	vAddr := fmt.Sprintf("%p", pv)
+	pvAddr := fmt.Sprintf("%p", &pv)
+	vt := "spew_test.customError"
+	vs := "error: 127"
+	addFormatterTest("%v", v, vs)
+	addFormatterTest("%v", pv, "<*>"+vs)
+	addFormatterTest("%v", &pv, "<**>"+vs)
+	addFormatterTest("%v", nv, "<nil>")
+	addFormatterTest("%+v", v, vs)
+	addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs)
+	addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%+v", nv, "<nil>")
+	addFormatterTest("%#v", v, "("+vt+")"+vs)
+	addFormatterTest("%#v", pv, "(*"+vt+")"+vs)
+	addFormatterTest("%#v", &pv, "(**"+vt+")"+vs)
+	addFormatterTest("%#v", nv, "(*"+vt+")"+"<nil>")
+	addFormatterTest("%#+v", v, "("+vt+")"+vs)
+	addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs)
+	addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs)
+	addFormatterTest("%#+v", nv, "(*"+vt+")"+"<nil>")
+}
+
+func addPassthroughFormatterTests() {
+	// %x passthrough with uint.
+	v := uint(4294967295)
+	pv := &v
+	vAddr := fmt.Sprintf("%x", pv)
+	pvAddr := fmt.Sprintf("%x", &pv)
+	vs := "ffffffff"
+	addFormatterTest("%x", v, vs)
+	addFormatterTest("%x", pv, vAddr)
+	addFormatterTest("%x", &pv, pvAddr)
+
+	// %#x passthrough with uint.
+	v2 := int(2147483647)
+	pv2 := &v2
+	v2Addr := fmt.Sprintf("%#x", pv2)
+	pv2Addr := fmt.Sprintf("%#x", &pv2)
+	v2s := "0x7fffffff"
+	addFormatterTest("%#x", v2, v2s)
+	addFormatterTest("%#x", pv2, v2Addr)
+	addFormatterTest("%#x", &pv2, pv2Addr)
+
+	// %f passthrough with precision.
+	addFormatterTest("%.2f", 3.1415, "3.14")
+	addFormatterTest("%.3f", 3.1415, "3.142")
+	addFormatterTest("%.4f", 3.1415, "3.1415")
+
+	// %f passthrough with width and precision.
+	addFormatterTest("%5.2f", 3.1415, " 3.14")
+	addFormatterTest("%6.3f", 3.1415, " 3.142")
+	addFormatterTest("%7.4f", 3.1415, " 3.1415")
+
+	// %d passthrough with width.
+	addFormatterTest("%3d", 127, "127")
+	addFormatterTest("%4d", 127, " 127")
+	addFormatterTest("%5d", 127, "  127")
+
+	// %q passthrough with string.
+	addFormatterTest("%q", "test", "\"test\"")
+}
+
+// TestFormatter executes all of the tests described by formatterTests.
+func TestFormatter(t *testing.T) {
+	// Setup tests.
+	addIntFormatterTests()
+	addUintFormatterTests()
+	addBoolFormatterTests()
+	addFloatFormatterTests()
+	addComplexFormatterTests()
+	addArrayFormatterTests()
+	addSliceFormatterTests()
+	addStringFormatterTests()
+	addInterfaceFormatterTests()
+	addMapFormatterTests()
+	addStructFormatterTests()
+	addUintptrFormatterTests()
+	addUnsafePointerFormatterTests()
+	addChanFormatterTests()
+	addFuncFormatterTests()
+	addCircularFormatterTests()
+	addPanicFormatterTests()
+	addErrorFormatterTests()
+	addPassthroughFormatterTests()
+
+	t.Logf("Running %d tests", len(formatterTests))
+	for i, test := range formatterTests {
+		buf := new(bytes.Buffer)
+		spew.Fprintf(buf, test.format, test.in)
+		s := buf.String()
+		if testFailed(s, test.wants) {
+			t.Errorf("Formatter #%d format: %s got: %s %s", i, test.format, s,
+				stringizeWants(test.wants))
+			continue
+		}
+	}
+}
+
+type testStruct struct {
+	x int
+}
+
+func (ts testStruct) String() string {
+	return fmt.Sprintf("ts.%d", ts.x)
+}
+
+type testStructP struct {
+	x int
+}
+
+func (ts *testStructP) String() string {
+	return fmt.Sprintf("ts.%d", ts.x)
+}
+
+func TestPrintSortedKeys(t *testing.T) {
+	cfg := spew.ConfigState{SortKeys: true}
+	s := cfg.Sprint(map[int]string{1: "1", 3: "3", 2: "2"})
+	expected := "map[1:1 2:2 3:3]"
+	if s != expected {
+		t.Errorf("Sorted keys mismatch 1:\n  %v %v", s, expected)
+	}
+
+	s = cfg.Sprint(map[stringer]int{"1": 1, "3": 3, "2": 2})
+	expected = "map[stringer 1:1 stringer 2:2 stringer 3:3]"
+	if s != expected {
+		t.Errorf("Sorted keys mismatch 2:\n  %v %v", s, expected)
+	}
+
+	s = cfg.Sprint(map[pstringer]int{pstringer("1"): 1, pstringer("3"): 3, pstringer("2"): 2})
+	expected = "map[stringer 1:1 stringer 2:2 stringer 3:3]"
+	if spew.UnsafeDisabled {
+		expected = "map[1:1 2:2 3:3]"
+	}
+	if s != expected {
+		t.Errorf("Sorted keys mismatch 3:\n  %v %v", s, expected)
+	}
+
+	s = cfg.Sprint(map[testStruct]int{testStruct{1}: 1, testStruct{3}: 3, testStruct{2}: 2})
+	expected = "map[ts.1:1 ts.2:2 ts.3:3]"
+	if s != expected {
+		t.Errorf("Sorted keys mismatch 4:\n  %v %v", s, expected)
+	}
+
+	if !spew.UnsafeDisabled {
+		s = cfg.Sprint(map[testStructP]int{testStructP{1}: 1, testStructP{3}: 3, testStructP{2}: 2})
+		expected = "map[ts.1:1 ts.2:2 ts.3:3]"
+		if s != expected {
+			t.Errorf("Sorted keys mismatch 5:\n  %v %v", s, expected)
+		}
+	}
+
+	s = cfg.Sprint(map[customError]int{customError(1): 1, customError(3): 3, customError(2): 2})
+	expected = "map[error: 1:1 error: 2:2 error: 3:3]"
+	if s != expected {
+		t.Errorf("Sorted keys mismatch 6:\n  %v %v", s, expected)
+	}
+}

+ 87 - 0
vendor/github.com/davecgh/go-spew/spew/internal_test.go

@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+This test file is part of the spew package rather than than the spew_test
+package because it needs access to internals to properly test certain cases
+which are not possible via the public interface since they should never happen.
+*/
+
+package spew
+
+import (
+	"bytes"
+	"reflect"
+	"testing"
+)
+
+// dummyFmtState implements a fake fmt.State to use for testing invalid
+// reflect.Value handling.  This is necessary because the fmt package catches
+// invalid values before invoking the formatter on them.
+type dummyFmtState struct {
+	bytes.Buffer
+}
+
+func (dfs *dummyFmtState) Flag(f int) bool {
+	if f == int('+') {
+		return true
+	}
+	return false
+}
+
+func (dfs *dummyFmtState) Precision() (int, bool) {
+	return 0, false
+}
+
+func (dfs *dummyFmtState) Width() (int, bool) {
+	return 0, false
+}
+
+// TestInvalidReflectValue ensures the dump and formatter code handles an
+// invalid reflect value properly.  This needs access to internal state since it
+// should never happen in real code and therefore can't be tested via the public
+// API.
+func TestInvalidReflectValue(t *testing.T) {
+	i := 1
+
+	// Dump invalid reflect value.
+	v := new(reflect.Value)
+	buf := new(bytes.Buffer)
+	d := dumpState{w: buf, cs: &Config}
+	d.dump(*v)
+	s := buf.String()
+	want := "<invalid>"
+	if s != want {
+		t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want)
+	}
+	i++
+
+	// Formatter invalid reflect value.
+	buf2 := new(dummyFmtState)
+	f := formatState{value: *v, cs: &Config, fs: buf2}
+	f.format(*v)
+	s = buf2.String()
+	want = "<invalid>"
+	if s != want {
+		t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want)
+	}
+}
+
+// SortValues makes the internal sortValues function available to the test
+// package.
+func SortValues(values []reflect.Value, cs *ConfigState) {
+	sortValues(values, cs)
+}

+ 102 - 0
vendor/github.com/davecgh/go-spew/spew/internalunsafe_test.go

@@ -0,0 +1,102 @@
+// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
+
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+// NOTE: Due to the following build constraints, this file will only be compiled
+// when the code is not running on Google App Engine, compiled by GopherJS, and
+// "-tags safe" is not added to the go build command line.  The "disableunsafe"
+// tag is deprecated and thus should not be used.
+// +build !js,!appengine,!safe,!disableunsafe
+
+/*
+This test file is part of the spew package rather than than the spew_test
+package because it needs access to internals to properly test certain cases
+which are not possible via the public interface since they should never happen.
+*/
+
+package spew
+
+import (
+	"bytes"
+	"reflect"
+	"testing"
+	"unsafe"
+)
+
+// changeKind uses unsafe to intentionally change the kind of a reflect.Value to
+// the maximum kind value which does not exist.  This is needed to test the
+// fallback code which punts to the standard fmt library for new types that
+// might get added to the language.
+func changeKind(v *reflect.Value, readOnly bool) {
+	rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag))
+	*rvf = *rvf | ((1<<flagKindWidth - 1) << flagKindShift)
+	if readOnly {
+		*rvf |= flagRO
+	} else {
+		*rvf &= ^uintptr(flagRO)
+	}
+}
+
+// TestAddedReflectValue tests functionaly of the dump and formatter code which
+// falls back to the standard fmt library for new types that might get added to
+// the language.
+func TestAddedReflectValue(t *testing.T) {
+	i := 1
+
+	// Dump using a reflect.Value that is exported.
+	v := reflect.ValueOf(int8(5))
+	changeKind(&v, false)
+	buf := new(bytes.Buffer)
+	d := dumpState{w: buf, cs: &Config}
+	d.dump(v)
+	s := buf.String()
+	want := "(int8) 5"
+	if s != want {
+		t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
+	}
+	i++
+
+	// Dump using a reflect.Value that is not exported.
+	changeKind(&v, true)
+	buf.Reset()
+	d.dump(v)
+	s = buf.String()
+	want = "(int8) <int8 Value>"
+	if s != want {
+		t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
+	}
+	i++
+
+	// Formatter using a reflect.Value that is exported.
+	changeKind(&v, false)
+	buf2 := new(dummyFmtState)
+	f := formatState{value: v, cs: &Config, fs: buf2}
+	f.format(v)
+	s = buf2.String()
+	want = "5"
+	if s != want {
+		t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
+	}
+	i++
+
+	// Formatter using a reflect.Value that is not exported.
+	changeKind(&v, true)
+	buf2.Reset()
+	f = formatState{value: v, cs: &Config, fs: buf2}
+	f.format(v)
+	s = buf2.String()
+	want = "<int8 Value>"
+	if s != want {
+		t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
+	}
+}

+ 320 - 0
vendor/github.com/davecgh/go-spew/spew/spew_test.go

@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+package spew_test
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"testing"
+
+	"github.com/davecgh/go-spew/spew"
+)
+
+// spewFunc is used to identify which public function of the spew package or
+// ConfigState a test applies to.
+type spewFunc int
+
+const (
+	fCSFdump spewFunc = iota
+	fCSFprint
+	fCSFprintf
+	fCSFprintln
+	fCSPrint
+	fCSPrintln
+	fCSSdump
+	fCSSprint
+	fCSSprintf
+	fCSSprintln
+	fCSErrorf
+	fCSNewFormatter
+	fErrorf
+	fFprint
+	fFprintln
+	fPrint
+	fPrintln
+	fSdump
+	fSprint
+	fSprintf
+	fSprintln
+)
+
+// Map of spewFunc values to names for pretty printing.
+var spewFuncStrings = map[spewFunc]string{
+	fCSFdump:        "ConfigState.Fdump",
+	fCSFprint:       "ConfigState.Fprint",
+	fCSFprintf:      "ConfigState.Fprintf",
+	fCSFprintln:     "ConfigState.Fprintln",
+	fCSSdump:        "ConfigState.Sdump",
+	fCSPrint:        "ConfigState.Print",
+	fCSPrintln:      "ConfigState.Println",
+	fCSSprint:       "ConfigState.Sprint",
+	fCSSprintf:      "ConfigState.Sprintf",
+	fCSSprintln:     "ConfigState.Sprintln",
+	fCSErrorf:       "ConfigState.Errorf",
+	fCSNewFormatter: "ConfigState.NewFormatter",
+	fErrorf:         "spew.Errorf",
+	fFprint:         "spew.Fprint",
+	fFprintln:       "spew.Fprintln",
+	fPrint:          "spew.Print",
+	fPrintln:        "spew.Println",
+	fSdump:          "spew.Sdump",
+	fSprint:         "spew.Sprint",
+	fSprintf:        "spew.Sprintf",
+	fSprintln:       "spew.Sprintln",
+}
+
+func (f spewFunc) String() string {
+	if s, ok := spewFuncStrings[f]; ok {
+		return s
+	}
+	return fmt.Sprintf("Unknown spewFunc (%d)", int(f))
+}
+
+// spewTest is used to describe a test to be performed against the public
+// functions of the spew package or ConfigState.
+type spewTest struct {
+	cs     *spew.ConfigState
+	f      spewFunc
+	format string
+	in     interface{}
+	want   string
+}
+
+// spewTests houses the tests to be performed against the public functions of
+// the spew package and ConfigState.
+//
+// These tests are only intended to ensure the public functions are exercised
+// and are intentionally not exhaustive of types.  The exhaustive type
+// tests are handled in the dump and format tests.
+var spewTests []spewTest
+
+// redirStdout is a helper function to return the standard output from f as a
+// byte slice.
+func redirStdout(f func()) ([]byte, error) {
+	tempFile, err := ioutil.TempFile("", "ss-test")
+	if err != nil {
+		return nil, err
+	}
+	fileName := tempFile.Name()
+	defer os.Remove(fileName) // Ignore error
+
+	origStdout := os.Stdout
+	os.Stdout = tempFile
+	f()
+	os.Stdout = origStdout
+	tempFile.Close()
+
+	return ioutil.ReadFile(fileName)
+}
+
+func initSpewTests() {
+	// Config states with various settings.
+	scsDefault := spew.NewDefaultConfig()
+	scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
+	scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
+	scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
+	scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
+	scsNoPtrAddr := &spew.ConfigState{DisablePointerAddresses: true}
+	scsNoCap := &spew.ConfigState{DisableCapacities: true}
+
+	// Variables for tests on types which implement Stringer interface with and
+	// without a pointer receiver.
+	ts := stringer("test")
+	tps := pstringer("test")
+
+	type ptrTester struct {
+		s *struct{}
+	}
+	tptr := &ptrTester{s: &struct{}{}}
+
+	// depthTester is used to test max depth handling for structs, array, slices
+	// and maps.
+	type depthTester struct {
+		ic    indirCir1
+		arr   [1]string
+		slice []string
+		m     map[string]int
+	}
+	dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"},
+		map[string]int{"one": 1}}
+
+	// Variable for tests on types which implement error interface.
+	te := customError(10)
+
+	spewTests = []spewTest{
+		{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"},
+		{scsDefault, fCSFprint, "", int16(32767), "32767"},
+		{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"},
+		{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"},
+		{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"},
+		{scsDefault, fCSPrintln, "", uint8(255), "255\n"},
+		{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"},
+		{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"},
+		{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"},
+		{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"},
+		{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"},
+		{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"},
+		{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"},
+		{scsDefault, fFprint, "", float32(3.14), "3.14"},
+		{scsDefault, fFprintln, "", float64(6.28), "6.28\n"},
+		{scsDefault, fPrint, "", true, "true"},
+		{scsDefault, fPrintln, "", false, "false\n"},
+		{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"},
+		{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"},
+		{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"},
+		{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"},
+		{scsNoMethods, fCSFprint, "", ts, "test"},
+		{scsNoMethods, fCSFprint, "", &ts, "<*>test"},
+		{scsNoMethods, fCSFprint, "", tps, "test"},
+		{scsNoMethods, fCSFprint, "", &tps, "<*>test"},
+		{scsNoPmethods, fCSFprint, "", ts, "stringer test"},
+		{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"},
+		{scsNoPmethods, fCSFprint, "", tps, "test"},
+		{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"},
+		{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"},
+		{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" +
+			" ic: (spew_test.indirCir1) {\n  <max depth reached>\n },\n" +
+			" arr: ([1]string) (len=1 cap=1) {\n  <max depth reached>\n },\n" +
+			" slice: ([]string) (len=1 cap=1) {\n  <max depth reached>\n },\n" +
+			" m: (map[string]int) (len=1) {\n  <max depth reached>\n }\n}\n"},
+		{scsContinue, fCSFprint, "", ts, "(stringer test) test"},
+		{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " +
+			"(len=4) (stringer test) \"test\"\n"},
+		{scsContinue, fCSFprint, "", te, "(error: 10) 10"},
+		{scsContinue, fCSFdump, "", te, "(spew_test.customError) " +
+			"(error: 10) 10\n"},
+		{scsNoPtrAddr, fCSFprint, "", tptr, "<*>{<*>{}}"},
+		{scsNoPtrAddr, fCSSdump, "", tptr, "(*spew_test.ptrTester)({\ns: (*struct {})({\n})\n})\n"},
+		{scsNoCap, fCSSdump, "", make([]string, 0, 10), "([]string) {\n}\n"},
+		{scsNoCap, fCSSdump, "", make([]string, 1, 10), "([]string) (len=1) {\n(string) \"\"\n}\n"},
+	}
+}
+
+// TestSpew executes all of the tests described by spewTests.
+func TestSpew(t *testing.T) {
+	initSpewTests()
+
+	t.Logf("Running %d tests", len(spewTests))
+	for i, test := range spewTests {
+		buf := new(bytes.Buffer)
+		switch test.f {
+		case fCSFdump:
+			test.cs.Fdump(buf, test.in)
+
+		case fCSFprint:
+			test.cs.Fprint(buf, test.in)
+
+		case fCSFprintf:
+			test.cs.Fprintf(buf, test.format, test.in)
+
+		case fCSFprintln:
+			test.cs.Fprintln(buf, test.in)
+
+		case fCSPrint:
+			b, err := redirStdout(func() { test.cs.Print(test.in) })
+			if err != nil {
+				t.Errorf("%v #%d %v", test.f, i, err)
+				continue
+			}
+			buf.Write(b)
+
+		case fCSPrintln:
+			b, err := redirStdout(func() { test.cs.Println(test.in) })
+			if err != nil {
+				t.Errorf("%v #%d %v", test.f, i, err)
+				continue
+			}
+			buf.Write(b)
+
+		case fCSSdump:
+			str := test.cs.Sdump(test.in)
+			buf.WriteString(str)
+
+		case fCSSprint:
+			str := test.cs.Sprint(test.in)
+			buf.WriteString(str)
+
+		case fCSSprintf:
+			str := test.cs.Sprintf(test.format, test.in)
+			buf.WriteString(str)
+
+		case fCSSprintln:
+			str := test.cs.Sprintln(test.in)
+			buf.WriteString(str)
+
+		case fCSErrorf:
+			err := test.cs.Errorf(test.format, test.in)
+			buf.WriteString(err.Error())
+
+		case fCSNewFormatter:
+			fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in))
+
+		case fErrorf:
+			err := spew.Errorf(test.format, test.in)
+			buf.WriteString(err.Error())
+
+		case fFprint:
+			spew.Fprint(buf, test.in)
+
+		case fFprintln:
+			spew.Fprintln(buf, test.in)
+
+		case fPrint:
+			b, err := redirStdout(func() { spew.Print(test.in) })
+			if err != nil {
+				t.Errorf("%v #%d %v", test.f, i, err)
+				continue
+			}
+			buf.Write(b)
+
+		case fPrintln:
+			b, err := redirStdout(func() { spew.Println(test.in) })
+			if err != nil {
+				t.Errorf("%v #%d %v", test.f, i, err)
+				continue
+			}
+			buf.Write(b)
+
+		case fSdump:
+			str := spew.Sdump(test.in)
+			buf.WriteString(str)
+
+		case fSprint:
+			str := spew.Sprint(test.in)
+			buf.WriteString(str)
+
+		case fSprintf:
+			str := spew.Sprintf(test.format, test.in)
+			buf.WriteString(str)
+
+		case fSprintln:
+			str := spew.Sprintln(test.in)
+			buf.WriteString(str)
+
+		default:
+			t.Errorf("%v #%d unrecognized function", test.f, i)
+			continue
+		}
+		s := buf.String()
+		if test.want != s {
+			t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want)
+			continue
+		}
+	}
+}

+ 82 - 0
vendor/github.com/davecgh/go-spew/spew/testdata/dumpcgo.go

@@ -0,0 +1,82 @@
+// Copyright (c) 2013 Dave Collins <dave@davec.name>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+// NOTE: Due to the following build constraints, this file will only be compiled
+// when both cgo is supported and "-tags testcgo" is added to the go test
+// command line.  This code should really only be in the dumpcgo_test.go file,
+// but unfortunately Go will not allow cgo in test files, so this is a
+// workaround to allow cgo types to be tested.  This configuration is used
+// because spew itself does not require cgo to run even though it does handle
+// certain cgo types specially.  Rather than forcing all clients to require cgo
+// and an external C compiler just to run the tests, this scheme makes them
+// optional.
+// +build cgo,testcgo
+
+package testdata
+
+/*
+#include <stdint.h>
+typedef unsigned char custom_uchar_t;
+
+char            *ncp = 0;
+char            *cp = "test";
+char             ca[6] = {'t', 'e', 's', 't', '2', '\0'};
+unsigned char    uca[6] = {'t', 'e', 's', 't', '3', '\0'};
+signed char      sca[6] = {'t', 'e', 's', 't', '4', '\0'};
+uint8_t          ui8ta[6] = {'t', 'e', 's', 't', '5', '\0'};
+custom_uchar_t   tuca[6] = {'t', 'e', 's', 't', '6', '\0'};
+*/
+import "C"
+
+// GetCgoNullCharPointer returns a null char pointer via cgo.  This is only
+// used for tests.
+func GetCgoNullCharPointer() interface{} {
+	return C.ncp
+}
+
+// GetCgoCharPointer returns a char pointer via cgo.  This is only used for
+// tests.
+func GetCgoCharPointer() interface{} {
+	return C.cp
+}
+
+// GetCgoCharArray returns a char array via cgo and the array's len and cap.
+// This is only used for tests.
+func GetCgoCharArray() (interface{}, int, int) {
+	return C.ca, len(C.ca), cap(C.ca)
+}
+
+// GetCgoUnsignedCharArray returns an unsigned char array via cgo and the
+// array's len and cap.  This is only used for tests.
+func GetCgoUnsignedCharArray() (interface{}, int, int) {
+	return C.uca, len(C.uca), cap(C.uca)
+}
+
+// GetCgoSignedCharArray returns a signed char array via cgo and the array's len
+// and cap.  This is only used for tests.
+func GetCgoSignedCharArray() (interface{}, int, int) {
+	return C.sca, len(C.sca), cap(C.sca)
+}
+
+// GetCgoUint8tArray returns a uint8_t array via cgo and the array's len and
+// cap.  This is only used for tests.
+func GetCgoUint8tArray() (interface{}, int, int) {
+	return C.ui8ta, len(C.ui8ta), cap(C.ui8ta)
+}
+
+// GetCgoTypdefedUnsignedCharArray returns a typedefed unsigned char array via
+// cgo and the array's len and cap.  This is only used for tests.
+func GetCgoTypdefedUnsignedCharArray() (interface{}, int, int) {
+	return C.tuca, len(C.tuca), cap(C.tuca)
+}

+ 61 - 0
vendor/github.com/davecgh/go-spew/test_coverage.txt

@@ -0,0 +1,61 @@
+
+github.com/davecgh/go-spew/spew/dump.go		 dumpState.dump			 100.00% (88/88)
+github.com/davecgh/go-spew/spew/format.go	 formatState.format		 100.00% (82/82)
+github.com/davecgh/go-spew/spew/format.go	 formatState.formatPtr		 100.00% (52/52)
+github.com/davecgh/go-spew/spew/dump.go		 dumpState.dumpPtr		 100.00% (44/44)
+github.com/davecgh/go-spew/spew/dump.go		 dumpState.dumpSlice		 100.00% (39/39)
+github.com/davecgh/go-spew/spew/common.go	 handleMethods			 100.00% (30/30)
+github.com/davecgh/go-spew/spew/common.go	 printHexPtr			 100.00% (18/18)
+github.com/davecgh/go-spew/spew/common.go	 unsafeReflectValue		 100.00% (13/13)
+github.com/davecgh/go-spew/spew/format.go	 formatState.constructOrigFormat 100.00% (12/12)
+github.com/davecgh/go-spew/spew/dump.go		 fdump				 100.00% (11/11)
+github.com/davecgh/go-spew/spew/format.go	 formatState.Format		 100.00% (11/11)
+github.com/davecgh/go-spew/spew/common.go	 init				 100.00% (10/10)
+github.com/davecgh/go-spew/spew/common.go	 printComplex			 100.00% (9/9)
+github.com/davecgh/go-spew/spew/common.go	 valuesSorter.Less		 100.00% (8/8)
+github.com/davecgh/go-spew/spew/format.go	 formatState.buildDefaultFormat	 100.00% (7/7)
+github.com/davecgh/go-spew/spew/format.go	 formatState.unpackValue	 100.00% (5/5)
+github.com/davecgh/go-spew/spew/dump.go		 dumpState.indent		 100.00% (4/4)
+github.com/davecgh/go-spew/spew/common.go	 catchPanic			 100.00% (4/4)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.convertArgs	 100.00% (4/4)
+github.com/davecgh/go-spew/spew/spew.go		 convertArgs			 100.00% (4/4)
+github.com/davecgh/go-spew/spew/format.go	 newFormatter			 100.00% (3/3)
+github.com/davecgh/go-spew/spew/dump.go		 Sdump				 100.00% (3/3)
+github.com/davecgh/go-spew/spew/common.go	 printBool			 100.00% (3/3)
+github.com/davecgh/go-spew/spew/common.go	 sortValues			 100.00% (3/3)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.Sdump		 100.00% (3/3)
+github.com/davecgh/go-spew/spew/dump.go		 dumpState.unpackValue		 100.00% (3/3)
+github.com/davecgh/go-spew/spew/spew.go		 Printf				 100.00% (1/1)
+github.com/davecgh/go-spew/spew/spew.go		 Println			 100.00% (1/1)
+github.com/davecgh/go-spew/spew/spew.go		 Sprint				 100.00% (1/1)
+github.com/davecgh/go-spew/spew/spew.go		 Sprintf			 100.00% (1/1)
+github.com/davecgh/go-spew/spew/spew.go		 Sprintln			 100.00% (1/1)
+github.com/davecgh/go-spew/spew/common.go	 printFloat			 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 NewDefaultConfig		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/common.go	 printInt			 100.00% (1/1)
+github.com/davecgh/go-spew/spew/common.go	 printUint			 100.00% (1/1)
+github.com/davecgh/go-spew/spew/common.go	 valuesSorter.Len		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/common.go	 valuesSorter.Swap		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.Errorf		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.Fprint		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.Fprintf		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.Fprintln		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.Print		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.Printf		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.Println		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.Sprint		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.Sprintf		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.Sprintln		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.NewFormatter	 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.Fdump		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/config.go	 ConfigState.Dump		 100.00% (1/1)
+github.com/davecgh/go-spew/spew/dump.go		 Fdump				 100.00% (1/1)
+github.com/davecgh/go-spew/spew/dump.go		 Dump				 100.00% (1/1)
+github.com/davecgh/go-spew/spew/spew.go		 Fprintln			 100.00% (1/1)
+github.com/davecgh/go-spew/spew/format.go	 NewFormatter			 100.00% (1/1)
+github.com/davecgh/go-spew/spew/spew.go		 Errorf				 100.00% (1/1)
+github.com/davecgh/go-spew/spew/spew.go		 Fprint				 100.00% (1/1)
+github.com/davecgh/go-spew/spew/spew.go		 Fprintf			 100.00% (1/1)
+github.com/davecgh/go-spew/spew/spew.go		 Print				 100.00% (1/1)
+github.com/davecgh/go-spew/spew			 ------------------------------- 100.00% (505/505)
+

+ 1536 - 0
vendor/github.com/docopt/docopt-go/docopt_test.go

@@ -0,0 +1,1536 @@
+/*
+Based of off docopt.py: https://github.com/docopt/docopt
+
+Licensed under terms of MIT license (see LICENSE-MIT)
+Copyright (c) 2013 Keith Batten, kbatten@gmail.com
+*/
+
+package docopt
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"reflect"
+	"regexp"
+	"strings"
+	"testing"
+)
+
+func TestPatternFlat(t *testing.T) {
+	q := patternList{
+		newArgument("N", nil),
+		newOption("-a", "", 0, false),
+		newArgument("M", nil)}
+	p, err := newRequired(
+		newOneOrMore(newArgument("N", nil)),
+		newOption("-a", "", 0, false),
+		newArgument("M", nil)).flat(patternDefault)
+	if reflect.DeepEqual(p, q) != true {
+		t.Error(err)
+	}
+
+	q = patternList{newOptionsShortcut()}
+	p, err = newRequired(
+		newOptional(newOptionsShortcut()),
+		newOptional(newOption("-a", "", 0, false))).flat(patternOptionSSHORTCUT)
+	if reflect.DeepEqual(p, q) != true {
+		t.Error(err)
+	}
+	return
+}
+
+func TestOption(t *testing.T) {
+	if !parseOption("-h").eq(newOption("-h", "", 0, false)) {
+		t.Fail()
+	}
+	if !parseOption("--help").eq(newOption("", "--help", 0, false)) {
+		t.Fail()
+	}
+	if !parseOption("-h --help").eq(newOption("-h", "--help", 0, false)) {
+		t.Fail()
+	}
+	if !parseOption("-h, --help").eq(newOption("-h", "--help", 0, false)) {
+		t.Fail()
+	}
+
+	if !parseOption("-h TOPIC").eq(newOption("-h", "", 1, false)) {
+		t.Fail()
+	}
+	if !parseOption("--help TOPIC").eq(newOption("", "--help", 1, false)) {
+		t.Fail()
+	}
+	if !parseOption("-h TOPIC --help TOPIC").eq(newOption("-h", "--help", 1, false)) {
+		t.Fail()
+	}
+	if !parseOption("-h TOPIC, --help TOPIC").eq(newOption("-h", "--help", 1, false)) {
+		t.Fail()
+	}
+	if !parseOption("-h TOPIC, --help=TOPIC").eq(newOption("-h", "--help", 1, false)) {
+		t.Fail()
+	}
+
+	if !parseOption("-h  Description...").eq(newOption("-h", "", 0, false)) {
+		t.Fail()
+	}
+	if !parseOption("-h --help  Description...").eq(newOption("-h", "--help", 0, false)) {
+		t.Fail()
+	}
+	if !parseOption("-h TOPIC  Description...").eq(newOption("-h", "", 1, false)) {
+		t.Fail()
+	}
+
+	if !parseOption("    -h").eq(newOption("-h", "", 0, false)) {
+		t.Fail()
+	}
+
+	if !parseOption("-h TOPIC  Description... [default: 2]").eq(newOption("-h", "", 1, "2")) {
+		t.Fail()
+	}
+	if !parseOption("-h TOPIC  Descripton... [default: topic-1]").eq(newOption("-h", "", 1, "topic-1")) {
+		t.Fail()
+	}
+	if !parseOption("--help=TOPIC  ... [default: 3.14]").eq(newOption("", "--help", 1, "3.14")) {
+		t.Fail()
+	}
+	if !parseOption("-h, --help=DIR  ... [default: ./]").eq(newOption("-h", "--help", 1, "./")) {
+		t.Fail()
+	}
+	if !parseOption("-h TOPIC  Descripton... [dEfAuLt: 2]").eq(newOption("-h", "", 1, "2")) {
+		t.Fail()
+	}
+	return
+}
+
+func TestOptionName(t *testing.T) {
+	if newOption("-h", "", 0, false).name != "-h" {
+		t.Fail()
+	}
+	if newOption("-h", "--help", 0, false).name != "--help" {
+		t.Fail()
+	}
+	if newOption("", "--help", 0, false).name != "--help" {
+		t.Fail()
+	}
+	return
+}
+
+func TestCommands(t *testing.T) {
+	if v, err := Parse("Usage: prog add", []string{"add"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"add": true}) != true {
+		t.Error(err)
+	}
+	if v, err := Parse("Usage: prog [add]", []string{}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"add": false}) != true {
+		t.Error(err)
+	}
+	if v, err := Parse("Usage: prog [add]", []string{"add"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"add": true}) != true {
+		t.Error(err)
+	}
+	if v, err := Parse("Usage: prog (add|rm)", []string{"add"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"add": true, "rm": false}) != true {
+		t.Error(err)
+	}
+	if v, err := Parse("Usage: prog (add|rm)", []string{"rm"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"add": false, "rm": true}) != true {
+		t.Error(err)
+	}
+	if v, err := Parse("Usage: prog a b", []string{"a", "b"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"a": true, "b": true}) != true {
+		t.Error(err)
+	}
+	_, err := Parse("Usage: prog a b", []string{"b", "a"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Error(err)
+	}
+	return
+}
+
+func TestFormalUsage(t *testing.T) {
+	doc := `
+    Usage: prog [-hv] ARG
+           prog N M
+
+    prog is a program`
+	usage := parseSection("usage:", doc)[0]
+	if usage != "Usage: prog [-hv] ARG\n           prog N M" {
+		t.FailNow()
+	}
+	formal, err := formalUsage(usage)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if formal != "( [-hv] ARG ) | ( N M )" {
+		t.Fail()
+	}
+	return
+}
+
+func TestParseArgv(t *testing.T) {
+	o := patternList{
+		newOption("-h", "", 0, false),
+		newOption("-v", "--verbose", 0, false),
+		newOption("-f", "--file", 1, false),
+	}
+
+	p, err := parseArgv(tokenListFromString(""), &o, false)
+	q := patternList{}
+	if reflect.DeepEqual(p, q) != true {
+		t.Error(err)
+	}
+
+	p, err = parseArgv(tokenListFromString("-h"), &o, false)
+	q = patternList{newOption("-h", "", 0, true)}
+	if reflect.DeepEqual(p, q) != true {
+		t.Error(err)
+	}
+
+	p, err = parseArgv(tokenListFromString("-h --verbose"), &o, false)
+	q = patternList{
+		newOption("-h", "", 0, true),
+		newOption("-v", "--verbose", 0, true),
+	}
+	if reflect.DeepEqual(p, q) != true {
+		t.Error(err)
+	}
+
+	p, err = parseArgv(tokenListFromString("-h --file f.txt"), &o, false)
+	q = patternList{
+		newOption("-h", "", 0, true),
+		newOption("-f", "--file", 1, "f.txt"),
+	}
+	if reflect.DeepEqual(p, q) != true {
+		t.Error(err)
+	}
+
+	p, err = parseArgv(tokenListFromString("-h --file f.txt arg"), &o, false)
+	q = patternList{
+		newOption("-h", "", 0, true),
+		newOption("-f", "--file", 1, "f.txt"),
+		newArgument("", "arg"),
+	}
+	if reflect.DeepEqual(p, q) != true {
+		t.Error(err)
+	}
+
+	p, err = parseArgv(tokenListFromString("-h --file f.txt arg arg2"), &o, false)
+	q = patternList{
+		newOption("-h", "", 0, true),
+		newOption("-f", "--file", 1, "f.txt"),
+		newArgument("", "arg"),
+		newArgument("", "arg2"),
+	}
+	if reflect.DeepEqual(p, q) != true {
+		t.Error(err)
+	}
+
+	p, err = parseArgv(tokenListFromString("-h arg -- -v"), &o, false)
+	q = patternList{
+		newOption("-h", "", 0, true),
+		newArgument("", "arg"),
+		newArgument("", "--"),
+		newArgument("", "-v"),
+	}
+	if reflect.DeepEqual(p, q) != true {
+		t.Error(err)
+	}
+}
+
+func TestParsePattern(t *testing.T) {
+	o := patternList{
+		newOption("-h", "", 0, false),
+		newOption("-v", "--verbose", 0, false),
+		newOption("-f", "--file", 1, false),
+	}
+
+	p, err := parsePattern("[ -h ]", &o)
+	q := newRequired(newOptional(newOption("-h", "", 0, false)))
+	if p.eq(q) != true {
+		t.Error(err)
+	}
+
+	p, err = parsePattern("[ ARG ... ]", &o)
+	q = newRequired(newOptional(
+		newOneOrMore(
+			newArgument("ARG", nil))))
+	if p.eq(q) != true {
+		t.Error(err)
+	}
+
+	p, err = parsePattern("[ -h | -v ]", &o)
+	q = newRequired(
+		newOptional(
+			newEither(
+				newOption("-h", "", 0, false),
+				newOption("-v", "--verbose", 0, false))))
+	if p.eq(q) != true {
+		t.Error(err)
+	}
+
+	p, err = parsePattern("( -h | -v [ --file <f> ] )", &o)
+	q = newRequired(
+		newRequired(
+			newEither(
+				newOption("-h", "", 0, false),
+				newRequired(
+					newOption("-v", "--verbose", 0, false),
+					newOptional(
+						newOption("-f", "--file", 1, nil))))))
+	if p.eq(q) != true {
+		t.Error(err)
+	}
+
+	p, err = parsePattern("(-h|-v[--file=<f>]N...)", &o)
+	q = newRequired(
+		newRequired(
+			newEither(
+				newOption("-h", "", 0, false),
+				newRequired(
+					newOption("-v", "--verbose", 0, false),
+					newOptional(
+						newOption("-f", "--file", 1, nil)),
+					newOneOrMore(
+						newArgument("N", nil))))))
+	if p.eq(q) != true {
+		t.Error(err)
+	}
+
+	p, err = parsePattern("(N [M | (K | L)] | O P)", &o)
+	q = newRequired(
+		newRequired(
+			newEither(
+				newRequired(
+					newArgument("N", nil),
+					newOptional(
+						newEither(
+							newArgument("M", nil),
+							newRequired(
+								newEither(
+									newArgument("K", nil),
+									newArgument("L", nil)))))),
+				newRequired(
+					newArgument("O", nil),
+					newArgument("P", nil)))))
+	if p.eq(q) != true {
+		t.Error(err)
+	}
+
+	p, err = parsePattern("[ -h ] [N]", &o)
+	q = newRequired(
+		newOptional(
+			newOption("-h", "", 0, false)),
+		newOptional(
+			newArgument("N", nil)))
+	if p.eq(q) != true {
+		t.Error(err)
+	}
+
+	p, err = parsePattern("[options]", &o)
+	q = newRequired(
+		newOptional(
+			newOptionsShortcut()))
+	if p.eq(q) != true {
+		t.Error(err)
+	}
+
+	p, err = parsePattern("[options] A", &o)
+	q = newRequired(
+		newOptional(
+			newOptionsShortcut()),
+		newArgument("A", nil))
+	if p.eq(q) != true {
+		t.Error(err)
+	}
+
+	p, err = parsePattern("-v [options]", &o)
+	q = newRequired(
+		newOption("-v", "--verbose", 0, false),
+		newOptional(
+			newOptionsShortcut()))
+	if p.eq(q) != true {
+		t.Error(err)
+	}
+
+	p, err = parsePattern("ADD", &o)
+	q = newRequired(newArgument("ADD", nil))
+	if p.eq(q) != true {
+		t.Error(err)
+	}
+
+	p, err = parsePattern("<add>", &o)
+	q = newRequired(newArgument("<add>", nil))
+	if p.eq(q) != true {
+		t.Error(err)
+	}
+
+	p, err = parsePattern("add", &o)
+	q = newRequired(newCommand("add", false))
+	if p.eq(q) != true {
+		t.Error(err)
+	}
+}
+
+func TestOptionMatch(t *testing.T) {
+	v, w, x := newOption("-a", "", 0, false).match(
+		&patternList{newOption("-a", "", 0, true)}, nil)
+	y := patternList{newOption("-a", "", 0, true)}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOption("-a", "", 0, false).match(
+		&patternList{newOption("-x", "", 0, false)}, nil)
+	y = patternList{newOption("-x", "", 0, false)}
+	if v != false ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, patternList{}) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOption("-a", "", 0, false).match(
+		&patternList{newOption("-x", "", 0, false)}, nil)
+	y = patternList{newOption("-x", "", 0, false)}
+	if v != false ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, patternList{}) != true {
+		t.Fail()
+	}
+	v, w, x = newOption("-a", "", 0, false).match(
+		&patternList{newArgument("N", nil)}, nil)
+	y = patternList{newArgument("N", nil)}
+	if v != false ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, patternList{}) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOption("-a", "", 0, false).match(
+		&patternList{
+			newOption("-x", "", 0, false),
+			newOption("-a", "", 0, false),
+			newArgument("N", nil)}, nil)
+	y = patternList{
+		newOption("-x", "", 0, false),
+		newArgument("N", nil)}
+	z := patternList{newOption("-a", "", 0, false)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOption("-a", "", 0, false).match(
+		&patternList{
+			newOption("-a", "", 0, true),
+			newOption("-a", "", 0, false)}, nil)
+	y = patternList{newOption("-a", "", 0, false)}
+	z = patternList{newOption("-a", "", 0, true)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+}
+
+func TestArgumentMatch(t *testing.T) {
+	v, w, x := newArgument("N", nil).match(
+		&patternList{newArgument("N", 9)}, nil)
+	y := patternList{newArgument("N", 9)}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	v, w, x = newArgument("N", nil).match(
+		&patternList{newOption("-x", "", 0, false)}, nil)
+	y = patternList{newOption("-x", "", 0, false)}
+	if v != false ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, patternList{}) != true {
+		t.Fail()
+	}
+
+	v, w, x = newArgument("N", nil).match(
+		&patternList{newOption("-x", "", 0, false),
+			newOption("-a", "", 0, false),
+			newArgument("", 5)}, nil)
+	y = patternList{newOption("-x", "", 0, false),
+		newOption("-a", "", 0, false)}
+	z := patternList{newArgument("N", 5)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+
+	v, w, x = newArgument("N", nil).match(
+		&patternList{newArgument("", 9),
+			newArgument("", 0)}, nil)
+	y = patternList{newArgument("", 0)}
+	z = patternList{newArgument("N", 9)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+}
+
+func TestCommandMatch(t *testing.T) {
+	v, w, x := newCommand("c", false).match(
+		&patternList{newArgument("", "c")}, nil)
+	y := patternList{newCommand("c", true)}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	v, w, x = newCommand("c", false).match(
+		&patternList{newOption("-x", "", 0, false)}, nil)
+	y = patternList{newOption("-x", "", 0, false)}
+	if v != false ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, patternList{}) != true {
+		t.Fail()
+	}
+
+	v, w, x = newCommand("c", false).match(
+		&patternList{
+			newOption("-x", "", 0, false),
+			newOption("-a", "", 0, false),
+			newArgument("", "c")}, nil)
+	y = patternList{newOption("-x", "", 0, false),
+		newOption("-a", "", 0, false)}
+	z := patternList{newCommand("c", true)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+
+	v, w, x = newEither(
+		newCommand("add", false),
+		newCommand("rm", false)).match(
+		&patternList{newArgument("", "rm")}, nil)
+	y = patternList{newCommand("rm", true)}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+}
+
+func TestOptionalMatch(t *testing.T) {
+	v, w, x := newOptional(newOption("-a", "", 0, false)).match(
+		&patternList{newOption("-a", "", 0, false)}, nil)
+	y := patternList{newOption("-a", "", 0, false)}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOptional(newOption("-a", "", 0, false)).match(
+		&patternList{}, nil)
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, patternList{}) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOptional(newOption("-a", "", 0, false)).match(
+		&patternList{newOption("-x", "", 0, false)}, nil)
+	y = patternList{newOption("-x", "", 0, false)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, patternList{}) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOptional(newOption("-a", "", 0, false),
+		newOption("-b", "", 0, false)).match(
+		&patternList{newOption("-a", "", 0, false)}, nil)
+	y = patternList{newOption("-a", "", 0, false)}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOptional(newOption("-a", "", 0, false),
+		newOption("-b", "", 0, false)).match(
+		&patternList{newOption("-b", "", 0, false)}, nil)
+	y = patternList{newOption("-b", "", 0, false)}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOptional(newOption("-a", "", 0, false),
+		newOption("-b", "", 0, false)).match(
+		&patternList{newOption("-x", "", 0, false)}, nil)
+	y = patternList{newOption("-x", "", 0, false)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, patternList{}) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOptional(newArgument("N", nil)).match(
+		&patternList{newArgument("", 9)}, nil)
+	y = patternList{newArgument("N", 9)}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOptional(newOption("-a", "", 0, false),
+		newOption("-b", "", 0, false)).match(
+		&patternList{newOption("-b", "", 0, false),
+			newOption("-x", "", 0, false),
+			newOption("-a", "", 0, false)}, nil)
+	y = patternList{newOption("-x", "", 0, false)}
+	z := patternList{newOption("-a", "", 0, false),
+		newOption("-b", "", 0, false)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+}
+
+func TestRequiredMatch(t *testing.T) {
+	v, w, x := newRequired(newOption("-a", "", 0, false)).match(
+		&patternList{newOption("-a", "", 0, false)}, nil)
+	y := patternList{newOption("-a", "", 0, false)}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	v, w, x = newRequired(newOption("-a", "", 0, false)).match(&patternList{}, nil)
+	if v != false ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, patternList{}) != true {
+		t.Fail()
+	}
+
+	v, w, x = newRequired(newOption("-a", "", 0, false)).match(
+		&patternList{newOption("-x", "", 0, false)}, nil)
+	y = patternList{newOption("-x", "", 0, false)}
+	if v != false ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, patternList{}) != true {
+		t.Fail()
+	}
+	v, w, x = newRequired(newOption("-a", "", 0, false),
+		newOption("-b", "", 0, false)).match(
+		&patternList{newOption("-a", "", 0, false)}, nil)
+	y = patternList{newOption("-a", "", 0, false)}
+	if v != false ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, patternList{}) != true {
+		t.Fail()
+	}
+}
+
+func TestEitherMatch(t *testing.T) {
+	v, w, x := newEither(
+		newOption("-a", "", 0, false),
+		newOption("-b", "", 0, false)).match(
+		&patternList{newOption("-a", "", 0, false)}, nil)
+	y := patternList{newOption("-a", "", 0, false)}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	v, w, x = newEither(
+		newOption("-a", "", 0, false),
+		newOption("-b", "", 0, false)).match(&patternList{
+		newOption("-a", "", 0, false),
+		newOption("-b", "", 0, false)}, nil)
+	y = patternList{newOption("-b", "", 0, false)}
+	z := patternList{newOption("-a", "", 0, false)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+
+	v, w, x = newEither(
+		newOption("-a", "", 0, false),
+		newOption("-b", "", 0, false)).match(&patternList{
+		newOption("-x", "", 0, false)}, nil)
+	y = patternList{newOption("-x", "", 0, false)}
+	z = patternList{}
+	if v != false ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+
+	v, w, x = newEither(
+		newOption("-a", "", 0, false),
+		newOption("-b", "", 0, false),
+		newOption("-c", "", 0, false)).match(&patternList{
+		newOption("-x", "", 0, false),
+		newOption("-b", "", 0, false)}, nil)
+	y = patternList{newOption("-x", "", 0, false)}
+	z = patternList{newOption("-b", "", 0, false)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+	v, w, x = newEither(
+		newArgument("M", nil),
+		newRequired(newArgument("N", nil),
+			newArgument("M", nil))).match(&patternList{
+		newArgument("", 1),
+		newArgument("", 2)}, nil)
+	y = patternList{}
+	z = patternList{newArgument("N", 1), newArgument("M", 2)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+}
+
+func TestOneOrMoreMatch(t *testing.T) {
+	v, w, x := newOneOrMore(newArgument("N", nil)).match(
+		&patternList{newArgument("", 9)}, nil)
+	y := patternList{newArgument("N", 9)}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOneOrMore(newArgument("N", nil)).match(
+		&patternList{}, nil)
+	y = patternList{}
+	z := patternList{}
+	if v != false ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOneOrMore(newArgument("N", nil)).match(
+		&patternList{newOption("-x", "", 0, false)}, nil)
+	y = patternList{newOption("-x", "", 0, false)}
+	z = patternList{}
+	if v != false ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOneOrMore(newArgument("N", nil)).match(
+		&patternList{newArgument("", 9), newArgument("", 8)}, nil)
+	y = patternList{}
+	z = patternList{newArgument("N", 9), newArgument("N", 8)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOneOrMore(newArgument("N", nil)).match(&patternList{
+		newArgument("", 9),
+		newOption("-x", "", 0, false),
+		newArgument("", 8)}, nil)
+	y = patternList{newOption("-x", "", 0, false)}
+	z = patternList{newArgument("N", 9), newArgument("N", 8)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOneOrMore(newOption("-a", "", 0, false)).match(&patternList{
+		newOption("-a", "", 0, false),
+		newArgument("", 8),
+		newOption("-a", "", 0, false)}, nil)
+	y = patternList{newArgument("", 8)}
+	z = patternList{newOption("-a", "", 0, false), newOption("-a", "", 0, false)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOneOrMore(newOption("-a", "", 0, false)).match(&patternList{
+		newArgument("", 8),
+		newOption("-x", "", 0, false)}, nil)
+	y = patternList{newArgument("", 8), newOption("-x", "", 0, false)}
+	z = patternList{}
+	if v != false ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOneOrMore(newRequired(newOption("-a", "", 0, false),
+		newArgument("N", nil))).match(&patternList{
+		newOption("-a", "", 0, false),
+		newArgument("", 1),
+		newOption("-x", "", 0, false),
+		newOption("-a", "", 0, false),
+		newArgument("", 2)}, nil)
+	y = patternList{newOption("-x", "", 0, false)}
+	z = patternList{newOption("-a", "", 0, false),
+		newArgument("N", 1),
+		newOption("-a", "", 0, false),
+		newArgument("N", 2)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+
+	v, w, x = newOneOrMore(newOptional(newArgument("N", nil))).match(
+		&patternList{newArgument("", 9)}, nil)
+	y = patternList{}
+	z = patternList{newArgument("N", 9)}
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+}
+
+func TestListArgumentMatch(t *testing.T) {
+	p := newRequired(
+		newArgument("N", nil),
+		newArgument("N", nil))
+	p.fix()
+	v, w, x := p.match(&patternList{newArgument("", "1"),
+		newArgument("", "2")}, nil)
+	y := patternList{newArgument("N", []string{"1", "2"})}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	p = newOneOrMore(newArgument("N", nil))
+	p.fix()
+	v, w, x = p.match(&patternList{newArgument("", "1"),
+		newArgument("", "2"), newArgument("", "3")}, nil)
+	y = patternList{newArgument("N", []string{"1", "2", "3"})}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	p = newRequired(newArgument("N", nil),
+		newOneOrMore(newArgument("N", nil)))
+	p.fix()
+	v, w, x = p.match(&patternList{
+		newArgument("", "1"),
+		newArgument("", "2"),
+		newArgument("", "3")}, nil)
+	y = patternList{newArgument("N", []string{"1", "2", "3"})}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	p = newRequired(newArgument("N", nil),
+		newRequired(newArgument("N", nil)))
+	p.fix()
+	v, w, x = p.match(&patternList{
+		newArgument("", "1"),
+		newArgument("", "2")}, nil)
+	y = patternList{newArgument("N", []string{"1", "2"})}
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+}
+
+func TestBasicPatternMatching(t *testing.T) {
+	// ( -a N [ -x Z ] )
+	p := newRequired(
+		newOption("-a", "", 0, false),
+		newArgument("N", nil),
+		newOptional(
+			newOption("-x", "", 0, false),
+			newArgument("Z", nil)))
+
+	// -a N
+	q := patternList{newOption("-a", "", 0, false), newArgument("", 9)}
+	y := patternList{newOption("-a", "", 0, false), newArgument("N", 9)}
+	v, w, x := p.match(&q, nil)
+	if v != true ||
+		reflect.DeepEqual(*w, patternList{}) != true ||
+		reflect.DeepEqual(*x, y) != true {
+		t.Fail()
+	}
+
+	// -a -x N Z
+	q = patternList{newOption("-a", "", 0, false),
+		newOption("-x", "", 0, false),
+		newArgument("", 9), newArgument("", 5)}
+	y = patternList{}
+	z := patternList{newOption("-a", "", 0, false), newArgument("N", 9),
+		newOption("-x", "", 0, false), newArgument("Z", 5)}
+	v, w, x = p.match(&q, nil)
+	if v != true ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+
+	// -x N Z  # BZZ!
+	q = patternList{newOption("-x", "", 0, false),
+		newArgument("", 9), newArgument("", 5)}
+	y = patternList{newOption("-x", "", 0, false),
+		newArgument("", 9), newArgument("", 5)}
+	z = patternList{}
+	v, w, x = p.match(&q, nil)
+	if v != false ||
+		reflect.DeepEqual(*w, y) != true ||
+		reflect.DeepEqual(*x, z) != true {
+		t.Fail()
+	}
+}
+
+func TestPatternEither(t *testing.T) {
+	p := newOption("-a", "", 0, false).transform()
+	q := newEither(newRequired(
+		newOption("-a", "", 0, false)))
+	if p.eq(q) != true {
+		t.Fail()
+	}
+
+	p = newArgument("A", nil).transform()
+	q = newEither(newRequired(
+		newArgument("A", nil)))
+	if p.eq(q) != true {
+		t.Fail()
+	}
+
+	p = newRequired(
+		newEither(
+			newOption("-a", "", 0, false),
+			newOption("-b", "", 0, false)),
+		newOption("-c", "", 0, false)).transform()
+	q = newEither(
+		newRequired(
+			newOption("-a", "", 0, false),
+			newOption("-c", "", 0, false)),
+		newRequired(
+			newOption("-b", "", 0, false),
+			newOption("-c", "", 0, false)))
+	if p.eq(q) != true {
+		t.Fail()
+	}
+
+	p = newOptional(newOption("-a", "", 0, false),
+		newEither(newOption("-b", "", 0, false),
+			newOption("-c", "", 0, false))).transform()
+	q = newEither(
+		newRequired(
+			newOption("-b", "", 0, false), newOption("-a", "", 0, false)),
+		newRequired(
+			newOption("-c", "", 0, false), newOption("-a", "", 0, false)))
+	if p.eq(q) != true {
+		t.Fail()
+	}
+
+	p = newEither(newOption("-x", "", 0, false),
+		newEither(newOption("-y", "", 0, false),
+			newOption("-z", "", 0, false))).transform()
+	q = newEither(
+		newRequired(newOption("-x", "", 0, false)),
+		newRequired(newOption("-y", "", 0, false)),
+		newRequired(newOption("-z", "", 0, false)))
+	if p.eq(q) != true {
+		t.Fail()
+	}
+
+	p = newOneOrMore(newArgument("N", nil),
+		newArgument("M", nil)).transform()
+	q = newEither(
+		newRequired(newArgument("N", nil), newArgument("M", nil),
+			newArgument("N", nil), newArgument("M", nil)))
+	if p.eq(q) != true {
+		t.Fail()
+	}
+}
+
+func TestPatternFixRepeatingArguments(t *testing.T) {
+	p := newOption("-a", "", 0, false)
+	p.fixRepeatingArguments()
+	if p.eq(newOption("-a", "", 0, false)) != true {
+		t.Fail()
+	}
+
+	p = newArgument("N", nil)
+	p.fixRepeatingArguments()
+	if p.eq(newArgument("N", nil)) != true {
+		t.Fail()
+	}
+
+	p = newRequired(
+		newArgument("N", nil),
+		newArgument("N", nil))
+	q := newRequired(
+		newArgument("N", []string{}),
+		newArgument("N", []string{}))
+	p.fixRepeatingArguments()
+	if p.eq(q) != true {
+		t.Fail()
+	}
+
+	p = newEither(
+		newArgument("N", nil),
+		newOneOrMore(newArgument("N", nil)))
+	q = newEither(
+		newArgument("N", []string{}),
+		newOneOrMore(newArgument("N", []string{})))
+	p.fix()
+	if p.eq(q) != true {
+		t.Fail()
+	}
+}
+
+func TestSet(t *testing.T) {
+	p := newArgument("N", nil)
+	q := newArgument("N", nil)
+	if reflect.DeepEqual(p, q) != true {
+		t.Fail()
+	}
+	pl := patternList{newArgument("N", nil), newArgument("N", nil)}
+	ql := patternList{newArgument("N", nil)}
+	if reflect.DeepEqual(pl.unique(), ql.unique()) != true {
+		t.Fail()
+	}
+}
+
+func TestPatternFixIdentities1(t *testing.T) {
+	p := newRequired(
+		newArgument("N", nil),
+		newArgument("N", nil))
+	if len(p.children) < 2 {
+		t.FailNow()
+	}
+	if p.children[0].eq(p.children[1]) != true {
+		t.Fail()
+	}
+	if p.children[0] == p.children[1] {
+		t.Fail()
+	}
+	p.fixIdentities(nil)
+	if p.children[0] != p.children[1] {
+		t.Fail()
+	}
+}
+
+func TestPatternFixIdentities2(t *testing.T) {
+	p := newRequired(
+		newOptional(
+			newArgument("X", nil),
+			newArgument("N", nil)),
+		newArgument("N", nil))
+	if len(p.children) < 2 {
+		t.FailNow()
+	}
+	if len(p.children[0].children) < 2 {
+		t.FailNow()
+	}
+	if p.children[0].children[1].eq(p.children[1]) != true {
+		t.Fail()
+	}
+	if p.children[0].children[1] == p.children[1] {
+		t.Fail()
+	}
+	p.fixIdentities(nil)
+	if p.children[0].children[1] != p.children[1] {
+		t.Fail()
+	}
+}
+
+func TestLongOptionsErrorHandling(t *testing.T) {
+	_, err := Parse("Usage: prog", []string{"--non-existent"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Error(fmt.Sprintf("(%s) %s", reflect.TypeOf(err), err))
+	}
+	_, err = Parse("Usage: prog [--version --verbose]\nOptions: --version\n --verbose",
+		[]string{"--ver"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Error(err)
+	}
+	_, err = Parse("Usage: prog --long\nOptions: --long ARG", []string{}, true, "", false, false)
+	if _, ok := err.(*LanguageError); !ok {
+		t.Error(err)
+	}
+	_, err = Parse("Usage: prog --long ARG\nOptions: --long ARG",
+		[]string{"--long"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Error(fmt.Sprintf("(%s) %s", reflect.TypeOf(err), err))
+	}
+	_, err = Parse("Usage: prog --long=ARG\nOptions: --long", []string{}, true, "", false, false)
+	if _, ok := err.(*LanguageError); !ok {
+		t.Error(err)
+	}
+	_, err = Parse("Usage: prog --long\nOptions: --long",
+		[]string{}, true, "--long=ARG", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Error(err)
+	}
+}
+
+func TestShortOptionsErrorHandling(t *testing.T) {
+	_, err := Parse("Usage: prog -x\nOptions: -x  this\n -x  that", []string{}, true, "", false, false)
+	if _, ok := err.(*LanguageError); !ok {
+		t.Error(fmt.Sprintf("(%s) %s", reflect.TypeOf(err), err))
+	}
+	_, err = Parse("Usage: prog", []string{"-x"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Error(err)
+	}
+	_, err = Parse("Usage: prog -o\nOptions: -o ARG", []string{}, true, "", false, false)
+	if _, ok := err.(*LanguageError); !ok {
+		t.Error(err)
+	}
+	_, err = Parse("Usage: prog -o ARG\nOptions: -o ARG", []string{"-o"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Error(err)
+	}
+}
+
+func TestMatchingParen(t *testing.T) {
+	_, err := Parse("Usage: prog [a [b]", []string{}, true, "", false, false)
+	if _, ok := err.(*LanguageError); !ok {
+		t.Error(err)
+	}
+	_, err = Parse("Usage: prog [a [b] ] c )", []string{}, true, "", false, false)
+	if _, ok := err.(*LanguageError); !ok {
+		t.Error(err)
+	}
+}
+
+func TestAllowDoubleDash(t *testing.T) {
+	if v, err := Parse("usage: prog [-o] [--] <arg>\noptions: -o", []string{"--", "-o"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-o": false, "<arg>": "-o", "--": true}) != true {
+		t.Error(err)
+	}
+	if v, err := Parse("usage: prog [-o] [--] <arg>\noptions: -o", []string{"-o", "1"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-o": true, "<arg>": "1", "--": false}) != true {
+		t.Error(err)
+	}
+	_, err := Parse("usage: prog [-o] <arg>\noptions:-o", []string{"-o"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok { //"--" is not allowed; FIXME?
+		t.Error(err)
+	}
+}
+
+func TestDocopt(t *testing.T) {
+	doc := `Usage: prog [-v] A
+
+                Options: -v  Be verbose.`
+	if v, err := Parse(doc, []string{"arg"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": false, "A": "arg"}) != true {
+		t.Error(err)
+	}
+	if v, err := Parse(doc, []string{"-v", "arg"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": true, "A": "arg"}) != true {
+		t.Error(err)
+	}
+
+	doc = `Usage: prog [-vqr] [FILE]
+              prog INPUT OUTPUT
+              prog --help
+
+    Options:
+      -v  print status messages
+      -q  report only file names
+      -r  show all occurrences of the same error
+      --help
+
+    `
+	if v, err := Parse(doc, []string{"-v", "file.py"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": true, "-q": false, "-r": false, "--help": false, "FILE": "file.py", "INPUT": nil, "OUTPUT": nil}) != true {
+		t.Error(err)
+	}
+	if v, err := Parse(doc, []string{"-v"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": true, "-q": false, "-r": false, "--help": false, "FILE": nil, "INPUT": nil, "OUTPUT": nil}) != true {
+		t.Error(err)
+	}
+
+	_, err := Parse(doc, []string{"-v", "input.py", "output.py"}, true, "", false, false) // does not match
+	if _, ok := err.(*UserError); !ok {
+		t.Error(err)
+	}
+	_, err = Parse(doc, []string{"--fake"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Error(err)
+	}
+	_, output, err := parseOutput(doc, []string{"--hel"}, true, "", false)
+	if err != nil || len(output) == 0 {
+		t.Error(err)
+	}
+}
+
+func TestLanguageErrors(t *testing.T) {
+	_, err := Parse("no usage with colon here", []string{}, true, "", false, false)
+	if _, ok := err.(*LanguageError); !ok {
+		t.Error(err)
+	}
+	_, err = Parse("usage: here \n\n and again usage: here", []string{}, true, "", false, false)
+	if _, ok := err.(*LanguageError); !ok {
+		t.Error(err)
+	}
+}
+
+func TestIssue40(t *testing.T) {
+	_, output, err := parseOutput("usage: prog --help-commands | --help", []string{"--help"}, true, "", false)
+	if err != nil || len(output) == 0 {
+		t.Error(err)
+	}
+	if v, err := Parse("usage: prog --aabb | --aa", []string{"--aa"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--aabb": false, "--aa": true}) != true {
+		t.Error(err)
+	}
+}
+
+func TestIssue34UnicodeStrings(t *testing.T) {
+	// TODO: see if applicable
+}
+
+func TestCountMultipleFlags(t *testing.T) {
+	if v, err := Parse("usage: prog [-v]", []string{"-v"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": true}) != true {
+		t.Error(err)
+	}
+	if v, err := Parse("usage: prog [-vv]", []string{}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": 0}) != true {
+		t.Error(err)
+	}
+	if v, err := Parse("usage: prog [-vv]", []string{"-v"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": 1}) != true {
+		t.Error(err)
+	}
+	if v, err := Parse("usage: prog [-vv]", []string{"-vv"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": 2}) != true {
+		t.Error(err)
+	}
+	_, err := Parse("usage: prog [-vv]", []string{"-vvv"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Error(err)
+	}
+	if v, err := Parse("usage: prog [-v | -vv | -vvv]", []string{"-vvv"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": 3}) != true {
+		t.Error(err)
+	}
+	if v, err := Parse("usage: prog [-v...]", []string{"-vvvvvv"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-v": 6}) != true {
+		t.Error(err)
+	}
+	if v, err := Parse("usage: prog [--ver --ver]", []string{"--ver", "--ver"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--ver": 2}) != true {
+		t.Error(err)
+	}
+}
+
+func TestAnyOptionsParameter(t *testing.T) {
+	_, err := Parse("usage: prog [options]",
+		[]string{"-foo", "--bar", "--spam=eggs"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Fail()
+	}
+
+	_, err = Parse("usage: prog [options]",
+		[]string{"--foo", "--bar", "--bar"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Fail()
+	}
+	_, err = Parse("usage: prog [options]",
+		[]string{"--bar", "--bar", "--bar", "-ffff"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Fail()
+	}
+	_, err = Parse("usage: prog [options]",
+		[]string{"--long=arg", "--long=another"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Fail()
+	}
+}
+
+func TestDefaultValueForPositionalArguments(t *testing.T) {
+	doc := "Usage: prog [--data=<data>...]\nOptions:\n\t-d --data=<arg>    Input data [default: x]"
+	if v, err := Parse(doc, []string{}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--data": []string{"x"}}) != true {
+		t.Error(err)
+	}
+
+	doc = "Usage: prog [--data=<data>...]\nOptions:\n\t-d --data=<arg>    Input data [default: x y]"
+	if v, err := Parse(doc, []string{}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--data": []string{"x", "y"}}) != true {
+		t.Error(err)
+	}
+
+	doc = "Usage: prog [--data=<data>...]\nOptions:\n\t-d --data=<arg>    Input data [default: x y]"
+	if v, err := Parse(doc, []string{"--data=this"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--data": []string{"this"}}) != true {
+		t.Error(err)
+	}
+}
+
+func TestIssue59(t *testing.T) {
+	if v, err := Parse("usage: prog --long=<a>", []string{"--long="}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--long": ""}) != true {
+		t.Error(err)
+	}
+
+	if v, err := Parse("usage: prog -l <a>\noptions: -l <a>", []string{"-l", ""}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"-l": ""}) != true {
+		t.Error(err)
+	}
+}
+
+func TestOptionsFirst(t *testing.T) {
+	if v, err := Parse("usage: prog [--opt] [<args>...]", []string{"--opt", "this", "that"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--opt": true, "<args>": []string{"this", "that"}}) != true {
+		t.Error(err)
+	}
+
+	if v, err := Parse("usage: prog [--opt] [<args>...]", []string{"this", "that", "--opt"}, true, "", false, false); reflect.DeepEqual(v, map[string]interface{}{"--opt": true, "<args>": []string{"this", "that"}}) != true {
+		t.Error(err)
+	}
+
+	if v, err := Parse("usage: prog [--opt] [<args>...]", []string{"this", "that", "--opt"}, true, "", true, false); reflect.DeepEqual(v, map[string]interface{}{"--opt": false, "<args>": []string{"this", "that", "--opt"}}) != true {
+		t.Error(err)
+	}
+}
+
+func TestIssue68OptionsShortcutDoesNotIncludeOptionsInUsagePattern(t *testing.T) {
+	args, err := Parse("usage: prog [-ab] [options]\noptions: -x\n -y", []string{"-ax"}, true, "", false, false)
+
+	if args["-a"] != true {
+		t.Error(err)
+	}
+	if args["-b"] != false {
+		t.Error(err)
+	}
+	if args["-x"] != true {
+		t.Error(err)
+	}
+	if args["-y"] != false {
+		t.Error(err)
+	}
+}
+
+func TestIssue65EvaluateArgvWhenCalledNotWhenImported(t *testing.T) {
+	os.Args = strings.Fields("prog -a")
+	v, err := Parse("usage: prog [-ab]", nil, true, "", false, false)
+	w := map[string]interface{}{"-a": true, "-b": false}
+	if reflect.DeepEqual(v, w) != true {
+		t.Error(err)
+	}
+
+	os.Args = strings.Fields("prog -b")
+	v, err = Parse("usage: prog [-ab]", nil, true, "", false, false)
+	w = map[string]interface{}{"-a": false, "-b": true}
+	if reflect.DeepEqual(v, w) != true {
+		t.Error(err)
+	}
+}
+
+func TestIssue71DoubleDashIsNotAValidOptionArgument(t *testing.T) {
+	_, err := Parse("usage: prog [--log=LEVEL] [--] <args>...",
+		[]string{"--log", "--", "1", "2"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Fail()
+	}
+
+	_, err = Parse(`usage: prog [-l LEVEL] [--] <args>...
+                  options: -l LEVEL`, []string{"-l", "--", "1", "2"}, true, "", false, false)
+	if _, ok := err.(*UserError); !ok {
+		t.Fail()
+	}
+}
+
+func TestParseSection(t *testing.T) {
+	v := parseSection("usage:", "foo bar fizz buzz")
+	w := []string{}
+	if reflect.DeepEqual(v, w) != true {
+		t.Fail()
+	}
+
+	v = parseSection("usage:", "usage: prog")
+	w = []string{"usage: prog"}
+	if reflect.DeepEqual(v, w) != true {
+		t.Fail()
+	}
+
+	v = parseSection("usage:", "usage: -x\n -y")
+	w = []string{"usage: -x\n -y"}
+	if reflect.DeepEqual(v, w) != true {
+		t.Fail()
+	}
+
+	usage := `usage: this
+
+usage:hai
+usage: this that
+
+usage: foo
+       bar
+
+PROGRAM USAGE:
+ foo
+ bar
+usage:
+` + "\t" + `too
+` + "\t" + `tar
+Usage: eggs spam
+BAZZ
+usage: pit stop`
+
+	v = parseSection("usage:", usage)
+	w = []string{"usage: this",
+		"usage:hai",
+		"usage: this that",
+		"usage: foo\n       bar",
+		"PROGRAM USAGE:\n foo\n bar",
+		"usage:\n\ttoo\n\ttar",
+		"Usage: eggs spam",
+		"usage: pit stop",
+	}
+	if reflect.DeepEqual(v, w) != true {
+		t.Fail()
+	}
+}
+
+func TestIssue126DefaultsNotParsedCorrectlyWhenTabs(t *testing.T) {
+	section := "Options:\n\t--foo=<arg>  [default: bar]"
+	v := patternList{newOption("", "--foo", 1, "bar")}
+	if reflect.DeepEqual(parseDefaults(section), v) != true {
+		t.Fail()
+	}
+}
+
+// conf file based test cases
+func TestFileTestcases(t *testing.T) {
+	filenames := []string{"testcases.docopt", "test_golang.docopt"}
+	for _, filename := range filenames {
+		raw, err := ioutil.ReadFile(filename)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		tests, err := parseTest(raw)
+		if err != nil {
+			t.Fatal(err)
+		}
+		for _, c := range tests {
+			result, err := Parse(c.doc, c.argv, true, "", false, false)
+			if _, ok := err.(*UserError); c.userError && !ok {
+				// expected a user-error
+				t.Error("testcase:", c.id, "result:", result)
+			} else if _, ok := err.(*UserError); !c.userError && ok {
+				// unexpected user-error
+				t.Error("testcase:", c.id, "error:", err, "result:", result)
+			} else if reflect.DeepEqual(c.expect, result) != true {
+				t.Error("testcase:", c.id, "result:", result, "expect:", c.expect)
+			}
+		}
+	}
+}
+
+type testcase struct {
+	id        int
+	doc       string
+	prog      string
+	argv      []string
+	expect    map[string]interface{}
+	userError bool
+}
+
+func parseTest(raw []byte) ([]testcase, error) {
+	var res []testcase
+	commentPattern := regexp.MustCompile("#.*")
+	raw = commentPattern.ReplaceAll(raw, []byte(""))
+	raw = bytes.TrimSpace(raw)
+	if bytes.HasPrefix(raw, []byte(`"""`)) {
+		raw = raw[3:]
+	}
+
+	id := 0
+	for _, fixture := range bytes.Split(raw, []byte(`r"""`)) {
+		doc, _, body := stringPartition(string(fixture), `"""`)
+		for _, cas := range strings.Split(body, "$")[1:] {
+			argvString, _, expectString := stringPartition(strings.TrimSpace(cas), "\n")
+			prog, _, argvString := stringPartition(strings.TrimSpace(argvString), " ")
+			argv := []string{}
+			if len(argvString) > 0 {
+				argv = strings.Fields(argvString)
+			}
+			var expectUntyped interface{}
+			err := json.Unmarshal([]byte(expectString), &expectUntyped)
+			if err != nil {
+				return nil, err
+			}
+			switch expect := expectUntyped.(type) {
+			case string: // user-error
+				res = append(res, testcase{id, doc, prog, argv, nil, true})
+			case map[string]interface{}:
+				// convert []interface{} values to []string
+				// convert float64 values to int
+				for k, vUntyped := range expect {
+					switch v := vUntyped.(type) {
+					case []interface{}:
+						itemList := make([]string, len(v))
+						for i, itemUntyped := range v {
+							if item, ok := itemUntyped.(string); ok {
+								itemList[i] = item
+							}
+						}
+						expect[k] = itemList
+					case float64:
+						expect[k] = int(v)
+					}
+				}
+				res = append(res, testcase{id, doc, prog, argv, expect, false})
+			default:
+				return nil, fmt.Errorf("unhandled json data type")
+			}
+			id++
+		}
+	}
+	return res, nil
+}
+
+// parseOutput wraps the Parse() function to also return stdout
+func parseOutput(doc string, argv []string, help bool, version string,
+	optionsFirst bool) (map[string]interface{}, string, error) {
+	stdout := os.Stdout
+	r, w, _ := os.Pipe()
+	os.Stdout = w
+
+	args, err := Parse(doc, argv, help, version, optionsFirst, false)
+
+	outChan := make(chan string)
+	go func() {
+		var buf bytes.Buffer
+		io.Copy(&buf, r)
+		outChan <- buf.String()
+	}()
+
+	w.Close()
+	os.Stdout = stdout
+	output := <-outChan
+
+	return args, output, err
+}
+
+var debugEnabled = false
+
+func debugOn(l ...interface{}) {
+	debugEnabled = true
+	debug(l...)
+}
+func debugOff(l ...interface{}) {
+	debug(l...)
+	debugEnabled = false
+}
+
+func debug(l ...interface{}) {
+	if debugEnabled {
+		fmt.Println(l...)
+	}
+}

+ 37 - 0
vendor/github.com/docopt/docopt-go/example_test.go

@@ -0,0 +1,37 @@
+package docopt
+
+import (
+	"fmt"
+	"sort"
+)
+
+func ExampleParse() {
+	usage := `Usage:
+  config_example tcp [<host>] [--force] [--timeout=<seconds>]
+  config_example serial <port> [--baud=<rate>] [--timeout=<seconds>]
+  config_example -h | --help | --version`
+	// parse the command line `comfig_example tcp 127.0.0.1 --force`
+	argv := []string{"tcp", "127.0.0.1", "--force"}
+	arguments, _ := Parse(usage, argv, true, "0.1.1rc", false)
+	// sort the keys of the arguments map
+	var keys []string
+	for k := range arguments {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	// print the argument keys and values
+	for _, k := range keys {
+		fmt.Printf("%9s %v\n", k, arguments[k])
+	}
+	// output:
+	//    --baud <nil>
+	//   --force true
+	//    --help false
+	// --timeout <nil>
+	// --version false
+	//        -h false
+	//    <host> 127.0.0.1
+	//    <port> <nil>
+	//    serial false
+	//       tcp true
+}

+ 29 - 0
vendor/github.com/docopt/docopt-go/examples/arguments/arguments_example.go

@@ -0,0 +1,29 @@
+package main
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `Usage: arguments_example [-vqrh] [FILE] ...
+       arguments_example (--left | --right) CORRECTION FILE
+
+Process FILE and optionally apply correction to either left-hand side or
+right-hand side.
+
+Arguments:
+  FILE        optional input file
+  CORRECTION  correction angle, needs FILE, --left or --right to be present
+
+Options:
+  -h --help
+  -v       verbose mode
+  -q       quiet mode
+  -r       make report
+  --left   use left-hand side
+  --right  use right-hand side`
+
+	arguments, _ := docopt.Parse(usage, nil, true, "", false)
+	fmt.Println(arguments)
+}

+ 26 - 0
vendor/github.com/docopt/docopt-go/examples/calculator/calculator_example.go

@@ -0,0 +1,26 @@
+package main
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `Not a serious example.
+
+Usage:
+  calculator_example <value> ( ( + | - | * | / ) <value> )...
+  calculator_example <function> <value> [( , <value> )]...
+  calculator_example (-h | --help)
+
+Examples:
+  calculator_example 1 + 2 + 3 + 4 + 5
+  calculator_example 1 + 2 '*' 3 / 4 - 5    # note quotes around '*'
+  calculator_example sum 10 , 20 , 30 , 40
+
+Options:
+  -h, --help
+`
+	arguments, _ := docopt.Parse(usage, nil, true, "", false)
+	fmt.Println(arguments)
+}

+ 76 - 0
vendor/github.com/docopt/docopt-go/examples/config_file/config_file_example.go

@@ -0,0 +1,76 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/docopt/docopt-go"
+	"strings"
+)
+
+func loadJSONConfig() map[string]interface{} {
+	var result map[string]interface{}
+	jsonData := []byte(`{"--force": true, "--timeout": "10", "--baud": "9600"}`)
+	json.Unmarshal(jsonData, &result)
+	return result
+}
+
+func loadIniConfig() map[string]interface{} {
+	iniData := `
+[default-arguments]
+--force
+--baud=19200
+<host>=localhost`
+	// trivial ini parser
+	// default value for an item is bool: true (for --force)
+	// otherwise the value is a string
+	iniParsed := make(map[string]map[string]interface{})
+	var section string
+	for _, line := range strings.Split(iniData, "\n") {
+		if strings.HasPrefix(line, "[") {
+			section = line
+			iniParsed[section] = make(map[string]interface{})
+		} else if section != "" {
+			kv := strings.SplitN(line, "=", 2)
+			if len(kv) == 1 {
+				iniParsed[section][kv[0]] = true
+			} else if len(kv) == 2 {
+				iniParsed[section][kv[0]] = kv[1]
+			}
+		}
+	}
+	return iniParsed["[default-arguments]"]
+}
+
+// merge combines two maps.
+// truthiness takes priority over falsiness
+// mapA takes priority over mapB
+func merge(mapA, mapB map[string]interface{}) map[string]interface{} {
+	result := make(map[string]interface{})
+	for k, v := range mapA {
+		result[k] = v
+	}
+	for k, v := range mapB {
+		if _, ok := result[k]; !ok || result[k] == nil || result[k] == false {
+			result[k] = v
+		}
+	}
+	return result
+}
+
+func main() {
+	usage := `Usage:
+  config_file_example tcp [<host>] [--force] [--timeout=<seconds>]
+  config_file_example serial <port> [--baud=<rate>] [--timeout=<seconds>]
+  config_file_example -h | --help | --version`
+
+	jsonConfig := loadJSONConfig()
+	iniConfig := loadIniConfig()
+	arguments, _ := docopt.Parse(usage, nil, true, "0.1.1rc", false)
+
+	// Arguments take priority over INI, INI takes priority over JSON
+	result := merge(arguments, merge(iniConfig, jsonConfig))
+
+	fmt.Println("JSON config: ", jsonConfig)
+	fmt.Println("INI config: ", iniConfig)
+	fmt.Println("Result: ", result)
+}

+ 22 - 0
vendor/github.com/docopt/docopt-go/examples/counted/counted_example.go

@@ -0,0 +1,22 @@
+package main
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `Usage: counted_example --help
+       counted_example -v...
+       counted_example go [go]
+       counted_example (--path=<path>)...
+       counted_example <file> <file>
+
+Try: counted_example -vvvvvvvvvv
+     counted_example go go
+     counted_example --path ./here --path ./there
+     counted_example this.txt that.txt`
+
+	arguments, _ := docopt.Parse(usage, nil, true, "", false)
+	fmt.Println(arguments)
+}

+ 38 - 0
vendor/github.com/docopt/docopt-go/examples/git/branch/git_branch.go

@@ -0,0 +1,38 @@
+package git
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `usage: git branch [options] [-r | -a] [--merged=<commit> | --no-merged=<commit>]
+       git branch [options] [-l] [-f] <branchname> [<start-point>]
+       git branch [options] [-r] (-d | -D) <branchname>
+       git branch [options] (-m | -M) [<oldbranch>] <newbranch>
+
+Generic options:
+    -h, --help
+    -v, --verbose         show hash and subject, give twice for upstream branch
+    -t, --track           set up tracking mode (see git-pull(1))
+    --set-upstream        change upstream info
+    --color=<when>        use colored output
+    -r                    act on remote-tracking branches
+    --contains=<commit>   print only branches that contain the commit
+    --abbrev=<n>          use <n> digits to display SHA-1s
+
+Specific git-branch actions:
+    -a                    list both remote-tracking and local branches
+    -d                    delete fully merged branch
+    -D                    delete branch (even if not merged)
+    -m                    move/rename a branch and its reflog
+    -M                    move/rename a branch, even if target exists
+    -l                    create the branch's reflog
+    -f, --force           force creation (when already exists)
+    --no-merged=<commit>  print only not merged branches
+    --merged=<commit>     print only merged branches
+`
+
+	args, _ := docopt.Parse(usage, nil, true, "", false)
+	fmt.Println(args)
+}

+ 30 - 0
vendor/github.com/docopt/docopt-go/examples/git/checkout/git_checkout.go

@@ -0,0 +1,30 @@
+package git
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `usage: git checkout [options] <branch>
+       git checkout [options] <branch> -- <file>...
+
+options:
+    -q, --quiet           suppress progress reporting
+    -b <branch>           create and checkout a new branch
+    -B <branch>           create/reset and checkout a branch
+    -l                    create reflog for new branch
+    -t, --track           set upstream info for new branch
+    --orphan <new branch>
+                          new unparented branch
+    -2, --ours            checkout our version for unmerged files
+    -3, --theirs          checkout their version for unmerged files
+    -f, --force           force checkout (throw away local modifications)
+    -m, --merge           perform a 3-way merge with the new branch
+    --conflict <style>    conflict style (merge or diff3)
+    -p, --patch           select hunks interactively
+`
+
+	args, _ := docopt.Parse(usage, nil, true, "", false)
+	fmt.Println(args)
+}

+ 37 - 0
vendor/github.com/docopt/docopt-go/examples/git/clone/git_clone.go

@@ -0,0 +1,37 @@
+package git
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `usage: git clone [options] [--] <repo> [<dir>]
+
+options:
+    -v, --verbose         be more verbose
+    -q, --quiet           be more quiet
+    --progress            force progress reporting
+    -n, --no-checkout     don't create a checkout
+    --bare                create a bare repository
+    --mirror              create a mirror repository (implies bare)
+    -l, --local           to clone from a local repository
+    --no-hardlinks        don't use local hardlinks, always copy
+    -s, --shared          setup as shared repository
+    --recursive           initialize submodules in the clone
+    --recurse-submodules  initialize submodules in the clone
+    --template <template-directory>
+                          directory from which templates will be used
+    --reference <repo>    reference repository
+    -o, --origin <branch>
+                          use <branch> instead of 'origin' to track upstream
+    -b, --branch <branch>
+                          checkout <branch> instead of the remote's HEAD
+    -u, --upload-pack <path>
+                          path to git-upload-pack on the remote
+    --depth <depth>       create a shallow clone of that depth
+`
+
+	args, _ := docopt.Parse(usage, nil, true, "", false)
+	fmt.Println(args)
+}

+ 108 - 0
vendor/github.com/docopt/docopt-go/examples/git/git.go

@@ -0,0 +1,108 @@
+package main
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+	"os"
+	"os/exec"
+)
+
+func main() {
+	usage := `usage: git [--version] [--exec-path=<path>] [--html-path]
+           [-p|--paginate|--no-pager] [--no-replace-objects]
+           [--bare] [--git-dir=<path>] [--work-tree=<path>]
+           [-c <name>=<value>] [--help]
+           <command> [<args>...]
+
+options:
+   -c <name=value>
+   -h, --help
+   -p, --paginate
+
+The most commonly used git commands are:
+   add        Add file contents to the index
+   branch     List, create, or delete branches
+   checkout   Checkout a branch or paths to the working tree
+   clone      Clone a repository into a new directory
+   commit     Record changes to the repository
+   push       Update remote refs along with associated objects
+   remote     Manage set of tracked repositories
+
+See 'git help <command>' for more information on a specific command.
+`
+	args, _ := docopt.Parse(usage, nil, true, "git version 1.7.4.4", true)
+
+	fmt.Println("global arguments:")
+	fmt.Println(args)
+
+	fmt.Println("command arguments:")
+	cmd := args["<command>"].(string)
+	cmdArgs := args["<args>"].([]string)
+
+	err := runCommand(cmd, cmdArgs)
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+}
+
+func goRun(scriptName string, args []string) (err error) {
+	cmdArgs := make([]string, 2)
+	cmdArgs[0] = "run"
+	cmdArgs[1] = scriptName
+	cmdArgs = append(cmdArgs, args...)
+	osCmd := exec.Command("go", cmdArgs...)
+	var out []byte
+	out, err = osCmd.Output()
+	fmt.Println(string(out))
+	if err != nil {
+		return
+	}
+	return
+}
+
+func runCommand(cmd string, args []string) (err error) {
+	argv := make([]string, 1)
+	argv[0] = cmd
+	argv = append(argv, args...)
+	switch cmd {
+	case "add":
+		// subcommand is a function call
+		return cmdAdd(argv)
+	case "branch":
+		// subcommand is a script
+		return goRun("branch/git_branch.go", argv)
+	case "checkout", "clone", "commit", "push", "remote":
+		// subcommand is a script
+		scriptName := fmt.Sprintf("%s/git_%s.go", cmd, cmd)
+		return goRun(scriptName, argv)
+	case "help", "":
+		return goRun("git.go", []string{"git_add.go", "--help"})
+	}
+
+	return fmt.Errorf("%s is not a git command. See 'git help'", cmd)
+}
+
+func cmdAdd(argv []string) (err error) {
+	usage := `usage: git add [options] [--] [<filepattern>...]
+
+options:
+	-h, --help
+	-n, --dry-run        dry run
+	-v, --verbose        be verbose
+	-i, --interactive    interactive picking
+	-p, --patch          select hunks interactively
+	-e, --edit           edit current diff and apply
+	-f, --force          allow adding otherwise ignored files
+	-u, --update         update tracked files
+	-N, --intent-to-add  record only the fact that the path will be added later
+	-A, --all            add all, noticing removal of tracked files
+	--refresh            don't add, only refresh the index
+	--ignore-errors      just skip files which cannot be added because of errors
+	--ignore-missing     check if - even missing - files are ignored in dry run
+`
+
+	args, _ := docopt.Parse(usage, nil, true, "", false)
+	fmt.Println(args)
+	return
+}

+ 34 - 0
vendor/github.com/docopt/docopt-go/examples/git/push/git_push.go

@@ -0,0 +1,34 @@
+package git
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `usage: git push [options] [<repository> [<refspec>...]]
+
+options:
+    -h, --help
+    -v, --verbose         be more verbose
+    -q, --quiet           be more quiet
+    --repo <repository>   repository
+    --all                 push all refs
+    --mirror              mirror all refs
+    --delete              delete refs
+    --tags                push tags (can't be used with --all or --mirror)
+    -n, --dry-run         dry run
+    --porcelain           machine-readable output
+    -f, --force           force updates
+    --thin                use thin pack
+    --receive-pack <receive-pack>
+                          receive pack program
+    --exec <receive-pack>
+                          receive pack program
+    -u, --set-upstream    set upstream for git pull/status
+    --progress            force progress reporting
+`
+
+	args, _ := docopt.Parse(usage, nil, true, "", false)
+	fmt.Println(args)
+}

+ 28 - 0
vendor/github.com/docopt/docopt-go/examples/git/remote/git_remote.go

@@ -0,0 +1,28 @@
+package git
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `usage: git remote [-v | --verbose]
+       git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>
+       git remote rename <old> <new>
+       git remote rm <name>
+       git remote set-head <name> (-a | -d | <branch>)
+       git remote [-v | --verbose] show [-n] <name>
+       git remote prune [-n | --dry-run] <name>
+       git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]
+       git remote set-branches <name> [--add] <branch>...
+       git remote set-url <name> <newurl> [<oldurl>]
+       git remote set-url --add <name> <newurl>
+       git remote set-url --delete <name> <url>
+
+options:
+    -v, --verbose         be verbose; must be placed before a subcommand
+`
+
+	args, _ := docopt.Parse(usage, nil, true, "", false)
+	fmt.Println(args)
+}

+ 28 - 0
vendor/github.com/docopt/docopt-go/examples/naval_fate/naval_fate.go

@@ -0,0 +1,28 @@
+package main
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `Naval Fate.
+
+Usage:
+  naval_fate ship new <name>...
+  naval_fate ship <name> move <x> <y> [--speed=<kn>]
+  naval_fate ship shoot <x> <y>
+  naval_fate mine (set|remove) <x> <y> [--moored|--drifting]
+  naval_fate -h | --help
+  naval_fate --version
+
+Options:
+  -h --help     Show this screen.
+  --version     Show version.
+  --speed=<kn>  Speed in knots [default: 10].
+  --moored      Moored (anchored) mine.
+  --drifting    Drifting mine.`
+
+	arguments, _ := docopt.Parse(usage, nil, true, "Naval Fate 2.0", false)
+	fmt.Println(arguments)
+}

+ 19 - 0
vendor/github.com/docopt/docopt-go/examples/odd_even/odd_even_example.go

@@ -0,0 +1,19 @@
+package main
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `Usage: odd_even_example [-h | --help] (ODD EVEN)...
+
+Example, try:
+  odd_even_example 1 2 3 4
+
+Options:
+  -h, --help`
+
+	arguments, _ := docopt.Parse(usage, nil, true, "", false)
+	fmt.Println(arguments)
+}

+ 43 - 0
vendor/github.com/docopt/docopt-go/examples/options/options_example.go

@@ -0,0 +1,43 @@
+package main
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `Example of program with many options using docopt.
+
+Usage:
+  options_example [-hvqrf NAME] [--exclude=PATTERNS]
+                     [--select=ERRORS | --ignore=ERRORS] [--show-source]
+                     [--statistics] [--count] [--benchmark] PATH...
+  options_example (--doctest | --testsuite=DIR)
+  options_example --version
+
+Arguments:
+  PATH  destination path
+
+Options:
+  -h --help            show this help message and exit
+  --version            show version and exit
+  -v --verbose         print status messages
+  -q --quiet           report only file names
+  -r --repeat          show all occurrences of the same error
+  --exclude=PATTERNS   exclude files or directories which match these comma
+                       separated patterns [default: .svn,CVS,.bzr,.hg,.git]
+  -f NAME --file=NAME  when parsing directories, only check filenames matching
+                       these comma separated patterns [default: *.go]
+  --select=ERRORS      select errors and warnings (e.g. E,W6)
+  --ignore=ERRORS      skip errors and warnings (e.g. E4,W)
+  --show-source        show source code for each error
+  --statistics         count errors and warnings
+  --count              print total number of errors and warnings to standard
+                       error and set exit code to 1 if total is not null
+  --benchmark          measure processing speed
+  --testsuite=DIR      run regression tests from dir
+  --doctest            run doctest on myself`
+
+	arguments, _ := docopt.Parse(usage, nil, true, "1.0.0rc2", false)
+	fmt.Println(arguments)
+}

+ 24 - 0
vendor/github.com/docopt/docopt-go/examples/options_shortcut/options_shortcut_example.go

@@ -0,0 +1,24 @@
+package main
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `Example of program which uses [options] shortcut in pattern.
+
+Usage:
+  options_shortcut_example [options] <port>
+
+Options:
+  -h --help                show this help message and exit
+  --version                show version and exit
+  -n, --number N           use N as a number
+  -t, --timeout TIMEOUT    set timeout TIMEOUT seconds
+  --apply                  apply changes to database
+  -q                       operate in quiet mode`
+
+	arguments, _ := docopt.Parse(usage, nil, true, "1.0.0rc2", false)
+	fmt.Println(arguments)
+}

+ 16 - 0
vendor/github.com/docopt/docopt-go/examples/quick/quick_example.go

@@ -0,0 +1,16 @@
+package main
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `Usage:
+  quick_example tcp <host> <port> [--timeout=<seconds>]
+  quick_example serial <port> [--baud=9600] [--timeout=<seconds>]
+  quick_example -h | --help | --version`
+
+	arguments, _ := docopt.Parse(usage, nil, true, "0.1.1rc", false)
+	fmt.Println(arguments)
+}

+ 31 - 0
vendor/github.com/docopt/docopt-go/examples/type_assert/type_assert_example.go

@@ -0,0 +1,31 @@
+package main
+
+import (
+	"fmt"
+	"github.com/docopt/docopt-go"
+)
+
+func main() {
+	usage := `usage: foo [-x] [-y]`
+
+	arguments, err := docopt.Parse(usage, nil, true, "", false)
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	fmt.Println(arguments)
+
+	var x = arguments["-x"].(bool) // type assertion required
+	if x == true {
+		fmt.Println("x is true")
+	}
+
+	y := arguments["-y"] // no type assertion needed
+	if y == true {
+		fmt.Println("y is true")
+	}
+	y2 := arguments["-y"]
+	if y2 == 10 { // this will never be true, a type assertion would have produced a build error
+		fmt.Println("y is 10")
+	}
+}

+ 17 - 0
vendor/github.com/fatedier/beego/.github/ISSUE_TEMPLATE

@@ -0,0 +1,17 @@
+Please answer these questions before submitting your issue. Thanks!
+
+1. What version of Go and beego are you using (`bee version`)?
+
+
+2. What operating system and processor architecture are you using (`go env`)?
+
+
+3. What did you do?
+If possible, provide a recipe for reproducing the error.
+A complete runnable program is good.
+
+
+4. What did you expect to see?
+
+
+5. What did you see instead?

+ 6 - 0
vendor/github.com/fatedier/beego/.gitignore

@@ -0,0 +1,6 @@
+.idea
+.vscode
+.DS_Store
+*.swp
+*.swo
+beego.iml

+ 51 - 0
vendor/github.com/fatedier/beego/.travis.yml

@@ -0,0 +1,51 @@
+language: go
+
+go:
+  - 1.6
+  - 1.5.3
+  - 1.4.3
+services:
+  - redis-server
+  - mysql
+  - postgresql
+  - memcached
+env:
+  - ORM_DRIVER=sqlite3   ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
+  - ORM_DRIVER=mysql    ORM_SOURCE="root:@/orm_test?charset=utf8"
+  - ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
+before_install:
+ - git clone git://github.com/ideawu/ssdb.git
+ - cd ssdb
+ - make
+ - cd ..
+install:
+  - go get github.com/lib/pq
+  - go get github.com/go-sql-driver/mysql
+  - go get github.com/mattn/go-sqlite3
+  - go get github.com/bradfitz/gomemcache/memcache
+  - go get github.com/garyburd/redigo/redis
+  - go get github.com/beego/x2j
+  - go get github.com/couchbase/go-couchbase
+  - go get github.com/beego/goyaml2
+  - go get github.com/belogik/goes
+  - go get github.com/siddontang/ledisdb/config
+  - go get github.com/siddontang/ledisdb/ledis
+  - go get github.com/ssdb/gossdb/ssdb
+  - go get github.com/cloudflare/golz4
+  - go get github.com/gogo/protobuf/proto
+before_script:
+  - psql --version
+  - sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"
+  - sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi"
+  - sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi"
+  - sh -c "if [ $(go version) == *1.[5-9]* ]; then go get github.com/golang/lint/golint; golint ./...; fi"
+  - sh -c "if [ $(go version) == *1.[5-9]* ]; then go tool vet .; fi"
+  - mkdir -p res/var
+  - ./ssdb/ssdb-server ./ssdb/ssdb.conf -d
+after_script:
+  -killall -w ssdb-server
+  - rm -rf ./res/var/*
+script:
+  - go test -v ./...
+addons:
+  postgresql: "9.4"

+ 52 - 0
vendor/github.com/fatedier/beego/CONTRIBUTING.md

@@ -0,0 +1,52 @@
+# Contributing to beego
+
+beego is an open source project.
+
+It is the work of hundreds of contributors. We appreciate your help!
+
+Here are instructions to get you started. They are probably not perfect, 
+please let us know if anything feels wrong or incomplete.
+
+## Contribution guidelines
+
+### Pull requests
+
+First of all. beego follow the gitflow. So please send you pull request 
+to **develop** branch. We will close the pull request to master branch.
+
+We are always happy to receive pull requests, and do our best to
+review them as fast as possible. Not sure if that typo is worth a pull
+request? Do it! We will appreciate it.
+
+If your pull request is not accepted on the first try, don't be
+discouraged! Sometimes we can make a mistake, please do more explaining 
+for us. We will appreciate it.
+
+We're trying very hard to keep beego simple and fast. We don't want it
+to do everything for everybody. This means that we might decide against
+incorporating a new feature. But we will give you some advice on how to 
+do it in other way.
+
+### Create issues
+
+Any significant improvement should be documented as [a GitHub
+issue](https://github.com/astaxie/beego/issues) before anybody
+starts working on it. 
+
+Also when filing an issue, make sure to answer these five questions:
+
+- What version of beego are you using (bee version)?
+- What operating system and processor architecture are you using?
+- What did you do?
+- What did you expect to see?
+- What did you see instead?
+
+### but check existing issues and docs first!
+
+Please take a moment to check that an issue doesn't already exist
+documenting your bug report or improvement proposal. If it does, it
+never hurts to add a quick "+1" or "I have this problem too". This will
+help prioritize the most common problems and requests.
+
+Also if you don't know how to use it. please make sure you have read though
+the docs in http://beego.me/docs

+ 62 - 0
vendor/github.com/fatedier/beego/README.md

@@ -0,0 +1,62 @@
+## Beego
+
+[![Build Status](https://travis-ci.org/astaxie/beego.svg?branch=master)](https://travis-ci.org/astaxie/beego)
+[![GoDoc](http://godoc.org/github.com/astaxie/beego?status.svg)](http://godoc.org/github.com/astaxie/beego)
+[![Foundation](https://img.shields.io/badge/Golang-Foundation-green.svg)](http://golangfoundation.org)
+
+beego is used for rapid development of RESTful APIs, web apps and backend services in Go.
+It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding.
+
+More info [beego.me](http://beego.me)
+
+##Quick Start
+######Download and install
+
+    go get github.com/astaxie/beego
+
+######Create file `hello.go`
+```go
+package main
+
+import "github.com/astaxie/beego"
+
+func main(){
+    beego.Run()
+}
+```
+######Build and run
+```bash
+    go build hello.go
+    ./hello
+```
+######Congratulations! 
+You just built your first beego app.
+Open your browser and visit `http://localhost:8080`.
+Please see [Documentation](http://beego.me/docs) for more.
+
+## Features
+
+* RESTful support
+* MVC architecture
+* Modularity
+* Auto API documents
+* Annotation router
+* Namespace
+* Powerful development tools
+* Full stack for Web & API
+
+## Documentation
+
+* [English](http://beego.me/docs/intro/)
+* [中文文档](http://beego.me/docs/intro/)
+* [Русский](http://beego.me/docs/intro/)
+
+## Community
+
+* [http://beego.me/community](http://beego.me/community)
+* Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited from [here](https://github.com/beego/beedoc/issues/232)
+
+## LICENSE
+
+beego source code is licensed under the Apache Licence, Version 2.0
+(http://www.apache.org/licenses/LICENSE-2.0.html).

+ 401 - 0
vendor/github.com/fatedier/beego/admin.go

@@ -0,0 +1,401 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package beego
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"os"
+	"text/template"
+	"time"
+
+	"reflect"
+
+	"github.com/astaxie/beego/grace"
+	"github.com/astaxie/beego/logs"
+	"github.com/astaxie/beego/toolbox"
+	"github.com/astaxie/beego/utils"
+)
+
+// BeeAdminApp is the default adminApp used by admin module.
+var beeAdminApp *adminApp
+
+// FilterMonitorFunc is default monitor filter when admin module is enable.
+// if this func returns, admin module records qbs for this request by condition of this function logic.
+// usage:
+// 	func MyFilterMonitor(method, requestPath string, t time.Duration) bool {
+//	 	if method == "POST" {
+//			return false
+//	 	}
+//	 	if t.Nanoseconds() < 100 {
+//			return false
+//	 	}
+//	 	if strings.HasPrefix(requestPath, "/astaxie") {
+//			return false
+//	 	}
+//	 	return true
+// 	}
+// 	beego.FilterMonitorFunc = MyFilterMonitor.
+var FilterMonitorFunc func(string, string, time.Duration) bool
+
+func init() {
+	beeAdminApp = &adminApp{
+		routers: make(map[string]http.HandlerFunc),
+	}
+	beeAdminApp.Route("/", adminIndex)
+	beeAdminApp.Route("/qps", qpsIndex)
+	beeAdminApp.Route("/prof", profIndex)
+	beeAdminApp.Route("/healthcheck", healthcheck)
+	beeAdminApp.Route("/task", taskStatus)
+	beeAdminApp.Route("/listconf", listConf)
+	FilterMonitorFunc = func(string, string, time.Duration) bool { return true }
+}
+
+// AdminIndex is the default http.Handler for admin module.
+// it matches url pattern "/".
+func adminIndex(rw http.ResponseWriter, r *http.Request) {
+	execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl)
+}
+
+// QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter.
+// it's registered with url pattern "/qbs" in admin module.
+func qpsIndex(rw http.ResponseWriter, r *http.Request) {
+	data := make(map[interface{}]interface{})
+	data["Content"] = toolbox.StatisticsMap.GetMap()
+	execTpl(rw, data, qpsTpl, defaultScriptsTpl)
+}
+
+// ListConf is the http.Handler of displaying all beego configuration values as key/value pair.
+// it's registered with url pattern "/listconf" in admin module.
+func listConf(rw http.ResponseWriter, r *http.Request) {
+	r.ParseForm()
+	command := r.Form.Get("command")
+	if command == "" {
+		rw.Write([]byte("command not support"))
+		return
+	}
+
+	data := make(map[interface{}]interface{})
+	switch command {
+	case "conf":
+		m := make(map[string]interface{})
+		list("BConfig", BConfig, m)
+		m["AppConfigPath"] = appConfigPath
+		m["AppConfigProvider"] = appConfigProvider
+		tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
+		tmpl = template.Must(tmpl.Parse(configTpl))
+		tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
+
+		data["Content"] = m
+
+		tmpl.Execute(rw, data)
+
+	case "router":
+		var (
+			content = map[string]interface{}{
+				"Fields": []string{
+					"Router Pattern",
+					"Methods",
+					"Controller",
+				},
+			}
+			methods     = []string{}
+			methodsData = make(map[string]interface{})
+		)
+		for method, t := range BeeApp.Handlers.routers {
+
+			resultList := new([][]string)
+
+			printTree(resultList, t)
+
+			methods = append(methods, method)
+			methodsData[method] = resultList
+		}
+
+		content["Data"] = methodsData
+		content["Methods"] = methods
+		data["Content"] = content
+		data["Title"] = "Routers"
+		execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
+	case "filter":
+		var (
+			content = map[string]interface{}{
+				"Fields": []string{
+					"Router Pattern",
+					"Filter Function",
+				},
+			}
+			filterTypes    = []string{}
+			filterTypeData = make(map[string]interface{})
+		)
+
+		if BeeApp.Handlers.enableFilter {
+			var filterType string
+			for k, fr := range map[int]string{
+				BeforeStatic: "Before Static",
+				BeforeRouter: "Before Router",
+				BeforeExec:   "Before Exec",
+				AfterExec:    "After Exec",
+				FinishRouter: "Finish Router"} {
+				if bf := BeeApp.Handlers.filters[k]; len(bf) > 0 {
+					filterType = fr
+					filterTypes = append(filterTypes, filterType)
+					resultList := new([][]string)
+					for _, f := range bf {
+						var result = []string{
+							fmt.Sprintf("%s", f.pattern),
+							fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
+						}
+						*resultList = append(*resultList, result)
+					}
+					filterTypeData[filterType] = resultList
+				}
+			}
+		}
+
+		content["Data"] = filterTypeData
+		content["Methods"] = filterTypes
+
+		data["Content"] = content
+		data["Title"] = "Filters"
+		execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
+	default:
+		rw.Write([]byte("command not support"))
+	}
+}
+
+func list(root string, p interface{}, m map[string]interface{}) {
+	pt := reflect.TypeOf(p)
+	pv := reflect.ValueOf(p)
+	if pt.Kind() == reflect.Ptr {
+		pt = pt.Elem()
+		pv = pv.Elem()
+	}
+	for i := 0; i < pv.NumField(); i++ {
+		var key string
+		if root == "" {
+			key = pt.Field(i).Name
+		} else {
+			key = root + "." + pt.Field(i).Name
+		}
+		if pv.Field(i).Kind() == reflect.Struct {
+			list(key, pv.Field(i).Interface(), m)
+		} else {
+			m[key] = pv.Field(i).Interface()
+		}
+	}
+}
+
+func printTree(resultList *[][]string, t *Tree) {
+	for _, tr := range t.fixrouters {
+		printTree(resultList, tr)
+	}
+	if t.wildcard != nil {
+		printTree(resultList, t.wildcard)
+	}
+	for _, l := range t.leaves {
+		if v, ok := l.runObject.(*controllerInfo); ok {
+			if v.routerType == routerTypeBeego {
+				var result = []string{
+					v.pattern,
+					fmt.Sprintf("%s", v.methods),
+					fmt.Sprintf("%s", v.controllerType),
+				}
+				*resultList = append(*resultList, result)
+			} else if v.routerType == routerTypeRESTFul {
+				var result = []string{
+					v.pattern,
+					fmt.Sprintf("%s", v.methods),
+					"",
+				}
+				*resultList = append(*resultList, result)
+			} else if v.routerType == routerTypeHandler {
+				var result = []string{
+					v.pattern,
+					"",
+					"",
+				}
+				*resultList = append(*resultList, result)
+			}
+		}
+	}
+}
+
+// ProfIndex is a http.Handler for showing profile command.
+// it's in url pattern "/prof" in admin module.
+func profIndex(rw http.ResponseWriter, r *http.Request) {
+	r.ParseForm()
+	command := r.Form.Get("command")
+	if command == "" {
+		return
+	}
+
+	var (
+		format = r.Form.Get("format")
+		data   = make(map[interface{}]interface{})
+		result bytes.Buffer
+	)
+	toolbox.ProcessInput(command, &result)
+	data["Content"] = result.String()
+
+	if format == "json" && command == "gc summary" {
+		dataJSON, err := json.Marshal(data)
+		if err != nil {
+			http.Error(rw, err.Error(), http.StatusInternalServerError)
+			return
+		}
+
+		rw.Header().Set("Content-Type", "application/json")
+		rw.Write(dataJSON)
+		return
+	}
+
+	data["Title"] = command
+	defaultTpl := defaultScriptsTpl
+	if command == "gc summary" {
+		defaultTpl = gcAjaxTpl
+	}
+	execTpl(rw, data, profillingTpl, defaultTpl)
+}
+
+// Healthcheck is a http.Handler calling health checking and showing the result.
+// it's in "/healthcheck" pattern in admin module.
+func healthcheck(rw http.ResponseWriter, req *http.Request) {
+	var (
+		data       = make(map[interface{}]interface{})
+		result     = []string{}
+		resultList = new([][]string)
+		content    = map[string]interface{}{
+			"Fields": []string{"Name", "Message", "Status"},
+		}
+	)
+
+	for name, h := range toolbox.AdminCheckList {
+		if err := h.Check(); err != nil {
+			result = []string{
+				fmt.Sprintf("error"),
+				fmt.Sprintf("%s", name),
+				fmt.Sprintf("%s", err.Error()),
+			}
+
+		} else {
+			result = []string{
+				fmt.Sprintf("success"),
+				fmt.Sprintf("%s", name),
+				fmt.Sprintf("OK"),
+			}
+
+		}
+		*resultList = append(*resultList, result)
+	}
+	content["Data"] = resultList
+	data["Content"] = content
+	data["Title"] = "Health Check"
+	execTpl(rw, data, healthCheckTpl, defaultScriptsTpl)
+}
+
+// TaskStatus is a http.Handler with running task status (task name, status and the last execution).
+// it's in "/task" pattern in admin module.
+func taskStatus(rw http.ResponseWriter, req *http.Request) {
+	data := make(map[interface{}]interface{})
+
+	// Run Task
+	req.ParseForm()
+	taskname := req.Form.Get("taskname")
+	if taskname != "" {
+		if t, ok := toolbox.AdminTaskList[taskname]; ok {
+			if err := t.Run(); err != nil {
+				data["Message"] = []string{"error", fmt.Sprintf("%s", err)}
+			}
+			data["Message"] = []string{"success", fmt.Sprintf("%s run success,Now the Status is <br>%s", taskname, t.GetStatus())}
+		} else {
+			data["Message"] = []string{"warning", fmt.Sprintf("there's no task which named: %s", taskname)}
+		}
+	}
+
+	// List Tasks
+	content := make(map[string]interface{})
+	resultList := new([][]string)
+	var result = []string{}
+	var fields = []string{
+		"Task Name",
+		"Task Spec",
+		"Task Status",
+		"Last Time",
+		"",
+	}
+	for tname, tk := range toolbox.AdminTaskList {
+		result = []string{
+			tname,
+			fmt.Sprintf("%s", tk.GetSpec()),
+			fmt.Sprintf("%s", tk.GetStatus()),
+			tk.GetPrev().String(),
+		}
+		*resultList = append(*resultList, result)
+	}
+
+	content["Fields"] = fields
+	content["Data"] = resultList
+	data["Content"] = content
+	data["Title"] = "Tasks"
+	execTpl(rw, data, tasksTpl, defaultScriptsTpl)
+}
+
+func execTpl(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) {
+	tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
+	for _, tpl := range tpls {
+		tmpl = template.Must(tmpl.Parse(tpl))
+	}
+	tmpl.Execute(rw, data)
+}
+
+// adminApp is an http.HandlerFunc map used as beeAdminApp.
+type adminApp struct {
+	routers map[string]http.HandlerFunc
+}
+
+// Route adds http.HandlerFunc to adminApp with url pattern.
+func (admin *adminApp) Route(pattern string, f http.HandlerFunc) {
+	admin.routers[pattern] = f
+}
+
+// Run adminApp http server.
+// Its addr is defined in configuration file as adminhttpaddr and adminhttpport.
+func (admin *adminApp) Run() {
+	if len(toolbox.AdminTaskList) > 0 {
+		toolbox.StartTask()
+	}
+	addr := BConfig.Listen.AdminAddr
+
+	if BConfig.Listen.AdminPort != 0 {
+		addr = fmt.Sprintf("%s:%d", BConfig.Listen.AdminAddr, BConfig.Listen.AdminPort)
+	}
+	for p, f := range admin.routers {
+		http.Handle(p, f)
+	}
+	logs.Info("Admin server Running on %s", addr)
+
+	var err error
+	if BConfig.Listen.Graceful {
+		err = grace.ListenAndServe(addr, nil)
+	} else {
+		err = http.ListenAndServe(addr, nil)
+	}
+	if err != nil {
+		logs.Critical("Admin ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
+	}
+}

+ 73 - 0
vendor/github.com/fatedier/beego/admin_test.go

@@ -0,0 +1,73 @@
+package beego
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestList_01(t *testing.T) {
+	m := make(map[string]interface{})
+	list("BConfig", BConfig, m)
+	t.Log(m)
+	om := oldMap()
+	for k, v := range om {
+		if fmt.Sprint(m[k]) != fmt.Sprint(v) {
+			t.Log(k, "old-key", v, "new-key", m[k])
+			t.FailNow()
+		}
+	}
+}
+
+func oldMap() map[string]interface{} {
+	m := make(map[string]interface{})
+	m["BConfig.AppName"] = BConfig.AppName
+	m["BConfig.RunMode"] = BConfig.RunMode
+	m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive
+	m["BConfig.ServerName"] = BConfig.ServerName
+	m["BConfig.RecoverPanic"] = BConfig.RecoverPanic
+	m["BConfig.CopyRequestBody"] = BConfig.CopyRequestBody
+	m["BConfig.EnableGzip"] = BConfig.EnableGzip
+	m["BConfig.MaxMemory"] = BConfig.MaxMemory
+	m["BConfig.EnableErrorsShow"] = BConfig.EnableErrorsShow
+	m["BConfig.Listen.Graceful"] = BConfig.Listen.Graceful
+	m["BConfig.Listen.ServerTimeOut"] = BConfig.Listen.ServerTimeOut
+	m["BConfig.Listen.ListenTCP4"] = BConfig.Listen.ListenTCP4
+	m["BConfig.Listen.EnableHTTP"] = BConfig.Listen.EnableHTTP
+	m["BConfig.Listen.HTTPAddr"] = BConfig.Listen.HTTPAddr
+	m["BConfig.Listen.HTTPPort"] = BConfig.Listen.HTTPPort
+	m["BConfig.Listen.EnableHTTPS"] = BConfig.Listen.EnableHTTPS
+	m["BConfig.Listen.HTTPSAddr"] = BConfig.Listen.HTTPSAddr
+	m["BConfig.Listen.HTTPSPort"] = BConfig.Listen.HTTPSPort
+	m["BConfig.Listen.HTTPSCertFile"] = BConfig.Listen.HTTPSCertFile
+	m["BConfig.Listen.HTTPSKeyFile"] = BConfig.Listen.HTTPSKeyFile
+	m["BConfig.Listen.EnableAdmin"] = BConfig.Listen.EnableAdmin
+	m["BConfig.Listen.AdminAddr"] = BConfig.Listen.AdminAddr
+	m["BConfig.Listen.AdminPort"] = BConfig.Listen.AdminPort
+	m["BConfig.Listen.EnableFcgi"] = BConfig.Listen.EnableFcgi
+	m["BConfig.Listen.EnableStdIo"] = BConfig.Listen.EnableStdIo
+	m["BConfig.WebConfig.AutoRender"] = BConfig.WebConfig.AutoRender
+	m["BConfig.WebConfig.EnableDocs"] = BConfig.WebConfig.EnableDocs
+	m["BConfig.WebConfig.FlashName"] = BConfig.WebConfig.FlashName
+	m["BConfig.WebConfig.FlashSeparator"] = BConfig.WebConfig.FlashSeparator
+	m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex
+	m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir
+	m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip
+	m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft
+	m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight
+	m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath
+	m["BConfig.WebConfig.EnableXSRF"] = BConfig.WebConfig.EnableXSRF
+	m["BConfig.WebConfig.XSRFExpire"] = BConfig.WebConfig.XSRFExpire
+	m["BConfig.WebConfig.Session.SessionOn"] = BConfig.WebConfig.Session.SessionOn
+	m["BConfig.WebConfig.Session.SessionProvider"] = BConfig.WebConfig.Session.SessionProvider
+	m["BConfig.WebConfig.Session.SessionName"] = BConfig.WebConfig.Session.SessionName
+	m["BConfig.WebConfig.Session.SessionGCMaxLifetime"] = BConfig.WebConfig.Session.SessionGCMaxLifetime
+	m["BConfig.WebConfig.Session.SessionProviderConfig"] = BConfig.WebConfig.Session.SessionProviderConfig
+	m["BConfig.WebConfig.Session.SessionCookieLifeTime"] = BConfig.WebConfig.Session.SessionCookieLifeTime
+	m["BConfig.WebConfig.Session.SessionAutoSetCookie"] = BConfig.WebConfig.Session.SessionAutoSetCookie
+	m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
+	m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly
+	m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs
+	m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum
+	m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
+	return m
+}

File diff suppressed because it is too large
+ 286 - 0
vendor/github.com/fatedier/beego/adminui.go


+ 366 - 0
vendor/github.com/fatedier/beego/app.go

@@ -0,0 +1,366 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package beego
+
+import (
+	"fmt"
+	"net"
+	"net/http"
+	"net/http/fcgi"
+	"os"
+	"path"
+	"time"
+
+	"github.com/astaxie/beego/grace"
+	"github.com/astaxie/beego/logs"
+	"github.com/astaxie/beego/utils"
+)
+
+var (
+	// BeeApp is an application instance
+	BeeApp *App
+)
+
+func init() {
+	// create beego application
+	BeeApp = NewApp()
+}
+
+// App defines beego application with a new PatternServeMux.
+type App struct {
+	Handlers *ControllerRegister
+	Server   *http.Server
+}
+
+// NewApp returns a new beego application.
+func NewApp() *App {
+	cr := NewControllerRegister()
+	app := &App{Handlers: cr, Server: &http.Server{}}
+	return app
+}
+
+// Run beego application.
+func (app *App) Run() {
+	addr := BConfig.Listen.HTTPAddr
+
+	if BConfig.Listen.HTTPPort != 0 {
+		addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPAddr, BConfig.Listen.HTTPPort)
+	}
+
+	var (
+		err        error
+		l          net.Listener
+		endRunning = make(chan bool, 1)
+	)
+
+	// run cgi server
+	if BConfig.Listen.EnableFcgi {
+		if BConfig.Listen.EnableStdIo {
+			if err = fcgi.Serve(nil, app.Handlers); err == nil { // standard I/O
+				logs.Info("Use FCGI via standard I/O")
+			} else {
+				logs.Critical("Cannot use FCGI via standard I/O", err)
+			}
+			return
+		}
+		if BConfig.Listen.HTTPPort == 0 {
+			// remove the Socket file before start
+			if utils.FileExists(addr) {
+				os.Remove(addr)
+			}
+			l, err = net.Listen("unix", addr)
+		} else {
+			l, err = net.Listen("tcp", addr)
+		}
+		if err != nil {
+			logs.Critical("Listen: ", err)
+		}
+		if err = fcgi.Serve(l, app.Handlers); err != nil {
+			logs.Critical("fcgi.Serve: ", err)
+		}
+		return
+	}
+
+	app.Server.Handler = app.Handlers
+	app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
+	app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
+	app.Server.ErrorLog = logs.GetLogger("HTTP")
+
+	// run graceful mode
+	if BConfig.Listen.Graceful {
+		httpsAddr := BConfig.Listen.HTTPSAddr
+		app.Server.Addr = httpsAddr
+		if BConfig.Listen.EnableHTTPS {
+			go func() {
+				time.Sleep(20 * time.Microsecond)
+				if BConfig.Listen.HTTPSPort != 0 {
+					httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
+					app.Server.Addr = httpsAddr
+				}
+				server := grace.NewServer(httpsAddr, app.Handlers)
+				server.Server.ReadTimeout = app.Server.ReadTimeout
+				server.Server.WriteTimeout = app.Server.WriteTimeout
+				if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
+					logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
+					time.Sleep(100 * time.Microsecond)
+					endRunning <- true
+				}
+			}()
+		}
+		if BConfig.Listen.EnableHTTP {
+			go func() {
+				server := grace.NewServer(addr, app.Handlers)
+				server.Server.ReadTimeout = app.Server.ReadTimeout
+				server.Server.WriteTimeout = app.Server.WriteTimeout
+				if BConfig.Listen.ListenTCP4 {
+					server.Network = "tcp4"
+				}
+				if err := server.ListenAndServe(); err != nil {
+					logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
+					time.Sleep(100 * time.Microsecond)
+					endRunning <- true
+				}
+			}()
+		}
+		<-endRunning
+		return
+	}
+
+	// run normal mode
+	if BConfig.Listen.EnableHTTPS {
+		go func() {
+			time.Sleep(20 * time.Microsecond)
+			if BConfig.Listen.HTTPSPort != 0 {
+				app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
+			} else if BConfig.Listen.EnableHTTP {
+				BeeLogger.Info("Start https server error, confict with http.Please reset https port")
+				return
+			}
+			logs.Info("https server Running on https://%s", app.Server.Addr)
+			if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
+				logs.Critical("ListenAndServeTLS: ", err)
+				time.Sleep(100 * time.Microsecond)
+				endRunning <- true
+			}
+		}()
+	}
+	if BConfig.Listen.EnableHTTP {
+		go func() {
+			app.Server.Addr = addr
+			logs.Info("http server Running on http://%s", app.Server.Addr)
+			if BConfig.Listen.ListenTCP4 {
+				ln, err := net.Listen("tcp4", app.Server.Addr)
+				if err != nil {
+					logs.Critical("ListenAndServe: ", err)
+					time.Sleep(100 * time.Microsecond)
+					endRunning <- true
+					return
+				}
+				if err = app.Server.Serve(ln); err != nil {
+					logs.Critical("ListenAndServe: ", err)
+					time.Sleep(100 * time.Microsecond)
+					endRunning <- true
+					return
+				}
+			} else {
+				if err := app.Server.ListenAndServe(); err != nil {
+					logs.Critical("ListenAndServe: ", err)
+					time.Sleep(100 * time.Microsecond)
+					endRunning <- true
+				}
+			}
+		}()
+	}
+	<-endRunning
+}
+
+// Router adds a patterned controller handler to BeeApp.
+// it's an alias method of App.Router.
+// usage:
+//  simple router
+//  beego.Router("/admin", &admin.UserController{})
+//  beego.Router("/admin/index", &admin.ArticleController{})
+//
+//  regex router
+//
+//  beego.Router("/api/:id([0-9]+)", &controllers.RController{})
+//
+//  custom rules
+//  beego.Router("/api/list",&RestController{},"*:ListFood")
+//  beego.Router("/api/create",&RestController{},"post:CreateFood")
+//  beego.Router("/api/update",&RestController{},"put:UpdateFood")
+//  beego.Router("/api/delete",&RestController{},"delete:DeleteFood")
+func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App {
+	BeeApp.Handlers.Add(rootpath, c, mappingMethods...)
+	return BeeApp
+}
+
+// Include will generate router file in the router/xxx.go from the controller's comments
+// usage:
+// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{})
+// type BankAccount struct{
+//   beego.Controller
+// }
+//
+// register the function
+// func (b *BankAccount)Mapping(){
+//  b.Mapping("ShowAccount" , b.ShowAccount)
+//  b.Mapping("ModifyAccount", b.ModifyAccount)
+//}
+//
+// //@router /account/:id  [get]
+// func (b *BankAccount) ShowAccount(){
+//    //logic
+// }
+//
+//
+// //@router /account/:id  [post]
+// func (b *BankAccount) ModifyAccount(){
+//    //logic
+// }
+//
+// the comments @router url methodlist
+// url support all the function Router's pattern
+// methodlist [get post head put delete options *]
+func Include(cList ...ControllerInterface) *App {
+	BeeApp.Handlers.Include(cList...)
+	return BeeApp
+}
+
+// RESTRouter adds a restful controller handler to BeeApp.
+// its' controller implements beego.ControllerInterface and
+// defines a param "pattern/:objectId" to visit each resource.
+func RESTRouter(rootpath string, c ControllerInterface) *App {
+	Router(rootpath, c)
+	Router(path.Join(rootpath, ":objectId"), c)
+	return BeeApp
+}
+
+// AutoRouter adds defined controller handler to BeeApp.
+// it's same to App.AutoRouter.
+// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page,
+// visit the url /main/list to exec List function or /main/page to exec Page function.
+func AutoRouter(c ControllerInterface) *App {
+	BeeApp.Handlers.AddAuto(c)
+	return BeeApp
+}
+
+// AutoPrefix adds controller handler to BeeApp with prefix.
+// it's same to App.AutoRouterWithPrefix.
+// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page,
+// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function.
+func AutoPrefix(prefix string, c ControllerInterface) *App {
+	BeeApp.Handlers.AddAutoPrefix(prefix, c)
+	return BeeApp
+}
+
+// Get used to register router for Get method
+// usage:
+//    beego.Get("/", func(ctx *context.Context){
+//          ctx.Output.Body("hello world")
+//    })
+func Get(rootpath string, f FilterFunc) *App {
+	BeeApp.Handlers.Get(rootpath, f)
+	return BeeApp
+}
+
+// Post used to register router for Post method
+// usage:
+//    beego.Post("/api", func(ctx *context.Context){
+//          ctx.Output.Body("hello world")
+//    })
+func Post(rootpath string, f FilterFunc) *App {
+	BeeApp.Handlers.Post(rootpath, f)
+	return BeeApp
+}
+
+// Delete used to register router for Delete method
+// usage:
+//    beego.Delete("/api", func(ctx *context.Context){
+//          ctx.Output.Body("hello world")
+//    })
+func Delete(rootpath string, f FilterFunc) *App {
+	BeeApp.Handlers.Delete(rootpath, f)
+	return BeeApp
+}
+
+// Put used to register router for Put method
+// usage:
+//    beego.Put("/api", func(ctx *context.Context){
+//          ctx.Output.Body("hello world")
+//    })
+func Put(rootpath string, f FilterFunc) *App {
+	BeeApp.Handlers.Put(rootpath, f)
+	return BeeApp
+}
+
+// Head used to register router for Head method
+// usage:
+//    beego.Head("/api", func(ctx *context.Context){
+//          ctx.Output.Body("hello world")
+//    })
+func Head(rootpath string, f FilterFunc) *App {
+	BeeApp.Handlers.Head(rootpath, f)
+	return BeeApp
+}
+
+// Options used to register router for Options method
+// usage:
+//    beego.Options("/api", func(ctx *context.Context){
+//          ctx.Output.Body("hello world")
+//    })
+func Options(rootpath string, f FilterFunc) *App {
+	BeeApp.Handlers.Options(rootpath, f)
+	return BeeApp
+}
+
+// Patch used to register router for Patch method
+// usage:
+//    beego.Patch("/api", func(ctx *context.Context){
+//          ctx.Output.Body("hello world")
+//    })
+func Patch(rootpath string, f FilterFunc) *App {
+	BeeApp.Handlers.Patch(rootpath, f)
+	return BeeApp
+}
+
+// Any used to register router for all methods
+// usage:
+//    beego.Any("/api", func(ctx *context.Context){
+//          ctx.Output.Body("hello world")
+//    })
+func Any(rootpath string, f FilterFunc) *App {
+	BeeApp.Handlers.Any(rootpath, f)
+	return BeeApp
+}
+
+// Handler used to register a Handler router
+// usage:
+//    beego.Handler("/api", func(ctx *context.Context){
+//          ctx.Output.Body("hello world")
+//    })
+func Handler(rootpath string, h http.Handler, options ...interface{}) *App {
+	BeeApp.Handlers.Handler(rootpath, h, options...)
+	return BeeApp
+}
+
+// InsertFilter adds a FilterFunc with pattern condition and action constant.
+// The pos means action constant including
+// beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter.
+// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute)
+func InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) *App {
+	BeeApp.Handlers.InsertFilter(pattern, pos, filter, params...)
+	return BeeApp
+}

+ 100 - 0
vendor/github.com/fatedier/beego/beego.go

@@ -0,0 +1,100 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package beego
+
+import (
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+)
+
+const (
+	// VERSION represent beego web framework version.
+	VERSION = "1.8.0"
+
+	// DEV is for develop
+	DEV = "dev"
+	// PROD is for production
+	PROD = "prod"
+)
+
+//hook function to run
+type hookfunc func() error
+
+var (
+	hooks = make([]hookfunc, 0) //hook function slice to store the hookfunc
+)
+
+// AddAPPStartHook is used to register the hookfunc
+// The hookfuncs will run in beego.Run()
+// such as sessionInit, middlerware start, buildtemplate, admin start
+func AddAPPStartHook(hf hookfunc) {
+	hooks = append(hooks, hf)
+}
+
+// Run beego application.
+// beego.Run() default run on HttpPort
+// beego.Run("localhost")
+// beego.Run(":8089")
+// beego.Run("127.0.0.1:8089")
+func Run(params ...string) {
+
+	initBeforeHTTPRun()
+
+	if len(params) > 0 && params[0] != "" {
+		strs := strings.Split(params[0], ":")
+		if len(strs) > 0 && strs[0] != "" {
+			BConfig.Listen.HTTPAddr = strs[0]
+		}
+		if len(strs) > 1 && strs[1] != "" {
+			BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
+		}
+	}
+
+	BeeApp.Run()
+}
+
+func initBeforeHTTPRun() {
+	//init hooks
+	AddAPPStartHook(registerMime)
+	AddAPPStartHook(registerDefaultErrorHandler)
+	AddAPPStartHook(registerSession)
+	AddAPPStartHook(registerTemplate)
+	AddAPPStartHook(registerAdmin)
+	AddAPPStartHook(registerGzip)
+
+	for _, hk := range hooks {
+		if err := hk(); err != nil {
+			panic(err)
+		}
+	}
+}
+
+// TestBeegoInit is for test package init
+func TestBeegoInit(ap string) {
+	path := filepath.Join(ap, "conf", "app.conf")
+	os.Chdir(ap)
+	InitBeegoBeforeTest(path)
+}
+
+// InitBeegoBeforeTest is for test package init
+func InitBeegoBeforeTest(appConfigPath string) {
+	if err := LoadAppConfig(appConfigProvider, appConfigPath); err != nil {
+		panic(err)
+	}
+	BConfig.RunMode = "test"
+	initBeforeHTTPRun()
+}

+ 59 - 0
vendor/github.com/fatedier/beego/cache/README.md

@@ -0,0 +1,59 @@
+## cache
+cache is a Go cache manager. It can use many cache adapters. The repo is inspired by `database/sql` .
+
+
+## How to install?
+
+	go get github.com/astaxie/beego/cache
+
+
+## What adapters are supported?
+
+As of now this cache support memory, Memcache and Redis.
+
+
+## How to use it?
+
+First you must import it
+
+	import (
+		"github.com/astaxie/beego/cache"
+	)
+
+Then init a Cache (example with memory adapter)
+
+	bm, err := cache.NewCache("memory", `{"interval":60}`)	
+
+Use it like this:	
+	
+	bm.Put("astaxie", 1, 10 * time.Second)
+	bm.Get("astaxie")
+	bm.IsExist("astaxie")
+	bm.Delete("astaxie")
+
+
+## Memory adapter
+
+Configure memory adapter like this:
+
+	{"interval":60}
+
+interval means the gc time. The cache will check at each time interval, whether item has expired.
+
+
+## Memcache adapter
+
+Memcache adapter use the [gomemcache](http://github.com/bradfitz/gomemcache) client.
+
+Configure like this:
+
+	{"conn":"127.0.0.1:11211"}
+
+
+## Redis adapter
+
+Redis adapter use the [redigo](http://github.com/garyburd/redigo) client.
+
+Configure like this:
+
+	{"conn":":6039"}

+ 103 - 0
vendor/github.com/fatedier/beego/cache/cache.go

@@ -0,0 +1,103 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package cache provide a Cache interface and some implemetn engine
+// Usage:
+//
+// import(
+//   "github.com/astaxie/beego/cache"
+// )
+//
+// bm, err := cache.NewCache("memory", `{"interval":60}`)
+//
+// Use it like this:
+//
+//	bm.Put("astaxie", 1, 10 * time.Second)
+//	bm.Get("astaxie")
+//	bm.IsExist("astaxie")
+//	bm.Delete("astaxie")
+//
+//  more docs http://beego.me/docs/module/cache.md
+package cache
+
+import (
+	"fmt"
+	"time"
+)
+
+// Cache interface contains all behaviors for cache adapter.
+// usage:
+//	cache.Register("file",cache.NewFileCache) // this operation is run in init method of file.go.
+//	c,err := cache.NewCache("file","{....}")
+//	c.Put("key",value, 3600 * time.Second)
+//	v := c.Get("key")
+//
+//	c.Incr("counter")  // now is 1
+//	c.Incr("counter")  // now is 2
+//	count := c.Get("counter").(int)
+type Cache interface {
+	// get cached value by key.
+	Get(key string) interface{}
+	// GetMulti is a batch version of Get.
+	GetMulti(keys []string) []interface{}
+	// set cached value with key and expire time.
+	Put(key string, val interface{}, timeout time.Duration) error
+	// delete cached value by key.
+	Delete(key string) error
+	// increase cached int value by key, as a counter.
+	Incr(key string) error
+	// decrease cached int value by key, as a counter.
+	Decr(key string) error
+	// check if cached value exists or not.
+	IsExist(key string) bool
+	// clear all cache.
+	ClearAll() error
+	// start gc routine based on config string settings.
+	StartAndGC(config string) error
+}
+
+// Instance is a function create a new Cache Instance
+type Instance func() Cache
+
+var adapters = make(map[string]Instance)
+
+// Register makes a cache adapter available by the adapter name.
+// If Register is called twice with the same name or if driver is nil,
+// it panics.
+func Register(name string, adapter Instance) {
+	if adapter == nil {
+		panic("cache: Register adapter is nil")
+	}
+	if _, ok := adapters[name]; ok {
+		panic("cache: Register called twice for adapter " + name)
+	}
+	adapters[name] = adapter
+}
+
+// NewCache Create a new cache driver by adapter name and config string.
+// config need to be correct JSON as string: {"interval":360}.
+// it will start gc automatically.
+func NewCache(adapterName, config string) (adapter Cache, err error) {
+	instanceFunc, ok := adapters[adapterName]
+	if !ok {
+		err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
+		return
+	}
+	adapter = instanceFunc()
+	err = adapter.StartAndGC(config)
+	if err != nil {
+		adapter = nil
+	}
+	return
+}

+ 168 - 0
vendor/github.com/fatedier/beego/cache/cache_test.go

@@ -0,0 +1,168 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cache
+
+import (
+	"os"
+	"testing"
+	"time"
+)
+
+func TestCache(t *testing.T) {
+	bm, err := NewCache("memory", `{"interval":20}`)
+	if err != nil {
+		t.Error("init err")
+	}
+	timeoutDuration := 10 * time.Second
+	if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if !bm.IsExist("astaxie") {
+		t.Error("check err")
+	}
+
+	if v := bm.Get("astaxie"); v.(int) != 1 {
+		t.Error("get err")
+	}
+
+	time.Sleep(30 * time.Second)
+
+	if bm.IsExist("astaxie") {
+		t.Error("check err")
+	}
+
+	if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+
+	if err = bm.Incr("astaxie"); err != nil {
+		t.Error("Incr Error", err)
+	}
+
+	if v := bm.Get("astaxie"); v.(int) != 2 {
+		t.Error("get err")
+	}
+
+	if err = bm.Decr("astaxie"); err != nil {
+		t.Error("Decr Error", err)
+	}
+
+	if v := bm.Get("astaxie"); v.(int) != 1 {
+		t.Error("get err")
+	}
+	bm.Delete("astaxie")
+	if bm.IsExist("astaxie") {
+		t.Error("delete err")
+	}
+
+	//test GetMulti
+	if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if !bm.IsExist("astaxie") {
+		t.Error("check err")
+	}
+	if v := bm.Get("astaxie"); v.(string) != "author" {
+		t.Error("get err")
+	}
+
+	if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if !bm.IsExist("astaxie1") {
+		t.Error("check err")
+	}
+
+	vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
+	if len(vv) != 2 {
+		t.Error("GetMulti ERROR")
+	}
+	if vv[0].(string) != "author" {
+		t.Error("GetMulti ERROR")
+	}
+	if vv[1].(string) != "author1" {
+		t.Error("GetMulti ERROR")
+	}
+}
+
+func TestFileCache(t *testing.T) {
+	bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}`)
+	if err != nil {
+		t.Error("init err")
+	}
+	timeoutDuration := 10 * time.Second
+	if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if !bm.IsExist("astaxie") {
+		t.Error("check err")
+	}
+
+	if v := bm.Get("astaxie"); v.(int) != 1 {
+		t.Error("get err")
+	}
+
+	if err = bm.Incr("astaxie"); err != nil {
+		t.Error("Incr Error", err)
+	}
+
+	if v := bm.Get("astaxie"); v.(int) != 2 {
+		t.Error("get err")
+	}
+
+	if err = bm.Decr("astaxie"); err != nil {
+		t.Error("Decr Error", err)
+	}
+
+	if v := bm.Get("astaxie"); v.(int) != 1 {
+		t.Error("get err")
+	}
+	bm.Delete("astaxie")
+	if bm.IsExist("astaxie") {
+		t.Error("delete err")
+	}
+
+	//test string
+	if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if !bm.IsExist("astaxie") {
+		t.Error("check err")
+	}
+	if v := bm.Get("astaxie"); v.(string) != "author" {
+		t.Error("get err")
+	}
+
+	//test GetMulti
+	if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if !bm.IsExist("astaxie1") {
+		t.Error("check err")
+	}
+
+	vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
+	if len(vv) != 2 {
+		t.Error("GetMulti ERROR")
+	}
+	if vv[0].(string) != "author" {
+		t.Error("GetMulti ERROR")
+	}
+	if vv[1].(string) != "author1" {
+		t.Error("GetMulti ERROR")
+	}
+
+	os.RemoveAll("cache")
+}

+ 100 - 0
vendor/github.com/fatedier/beego/cache/conv.go

@@ -0,0 +1,100 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cache
+
+import (
+	"fmt"
+	"strconv"
+)
+
+// GetString convert interface to string.
+func GetString(v interface{}) string {
+	switch result := v.(type) {
+	case string:
+		return result
+	case []byte:
+		return string(result)
+	default:
+		if v != nil {
+			return fmt.Sprintf("%v", result)
+		}
+	}
+	return ""
+}
+
+// GetInt convert interface to int.
+func GetInt(v interface{}) int {
+	switch result := v.(type) {
+	case int:
+		return result
+	case int32:
+		return int(result)
+	case int64:
+		return int(result)
+	default:
+		if d := GetString(v); d != "" {
+			value, _ := strconv.Atoi(d)
+			return value
+		}
+	}
+	return 0
+}
+
+// GetInt64 convert interface to int64.
+func GetInt64(v interface{}) int64 {
+	switch result := v.(type) {
+	case int:
+		return int64(result)
+	case int32:
+		return int64(result)
+	case int64:
+		return result
+	default:
+
+		if d := GetString(v); d != "" {
+			value, _ := strconv.ParseInt(d, 10, 64)
+			return value
+		}
+	}
+	return 0
+}
+
+// GetFloat64 convert interface to float64.
+func GetFloat64(v interface{}) float64 {
+	switch result := v.(type) {
+	case float64:
+		return result
+	default:
+		if d := GetString(v); d != "" {
+			value, _ := strconv.ParseFloat(d, 64)
+			return value
+		}
+	}
+	return 0
+}
+
+// GetBool convert interface to bool.
+func GetBool(v interface{}) bool {
+	switch result := v.(type) {
+	case bool:
+		return result
+	default:
+		if d := GetString(v); d != "" {
+			value, _ := strconv.ParseBool(d)
+			return value
+		}
+	}
+	return false
+}

+ 143 - 0
vendor/github.com/fatedier/beego/cache/conv_test.go

@@ -0,0 +1,143 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cache
+
+import (
+	"testing"
+)
+
+func TestGetString(t *testing.T) {
+	var t1 = "test1"
+	if "test1" != GetString(t1) {
+		t.Error("get string from string error")
+	}
+	var t2 = []byte("test2")
+	if "test2" != GetString(t2) {
+		t.Error("get string from byte array error")
+	}
+	var t3 = 1
+	if "1" != GetString(t3) {
+		t.Error("get string from int error")
+	}
+	var t4 int64 = 1
+	if "1" != GetString(t4) {
+		t.Error("get string from int64 error")
+	}
+	var t5 = 1.1
+	if "1.1" != GetString(t5) {
+		t.Error("get string from float64 error")
+	}
+
+	if "" != GetString(nil) {
+		t.Error("get string from nil error")
+	}
+}
+
+func TestGetInt(t *testing.T) {
+	var t1 = 1
+	if 1 != GetInt(t1) {
+		t.Error("get int from int error")
+	}
+	var t2 int32 = 32
+	if 32 != GetInt(t2) {
+		t.Error("get int from int32 error")
+	}
+	var t3 int64 = 64
+	if 64 != GetInt(t3) {
+		t.Error("get int from int64 error")
+	}
+	var t4 = "128"
+	if 128 != GetInt(t4) {
+		t.Error("get int from num string error")
+	}
+	if 0 != GetInt(nil) {
+		t.Error("get int from nil error")
+	}
+}
+
+func TestGetInt64(t *testing.T) {
+	var i int64 = 1
+	var t1 = 1
+	if i != GetInt64(t1) {
+		t.Error("get int64 from int error")
+	}
+	var t2 int32 = 1
+	if i != GetInt64(t2) {
+		t.Error("get int64 from int32 error")
+	}
+	var t3 int64 = 1
+	if i != GetInt64(t3) {
+		t.Error("get int64 from int64 error")
+	}
+	var t4 = "1"
+	if i != GetInt64(t4) {
+		t.Error("get int64 from num string error")
+	}
+	if 0 != GetInt64(nil) {
+		t.Error("get int64 from nil")
+	}
+}
+
+func TestGetFloat64(t *testing.T) {
+	var f = 1.11
+	var t1 float32 = 1.11
+	if f != GetFloat64(t1) {
+		t.Error("get float64 from float32 error")
+	}
+	var t2 = 1.11
+	if f != GetFloat64(t2) {
+		t.Error("get float64 from float64 error")
+	}
+	var t3 = "1.11"
+	if f != GetFloat64(t3) {
+		t.Error("get float64 from string error")
+	}
+
+	var f2 float64 = 1
+	var t4 = 1
+	if f2 != GetFloat64(t4) {
+		t.Error("get float64 from int error")
+	}
+
+	if 0 != GetFloat64(nil) {
+		t.Error("get float64 from nil error")
+	}
+}
+
+func TestGetBool(t *testing.T) {
+	var t1 = true
+	if true != GetBool(t1) {
+		t.Error("get bool from bool error")
+	}
+	var t2 = "true"
+	if true != GetBool(t2) {
+		t.Error("get bool from string error")
+	}
+	if false != GetBool(nil) {
+		t.Error("get bool from nil error")
+	}
+}
+
+func byteArrayEquals(a []byte, b []byte) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i, v := range a {
+		if v != b[i] {
+			return false
+		}
+	}
+	return true
+}

+ 255 - 0
vendor/github.com/fatedier/beego/cache/file.go

@@ -0,0 +1,255 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cache
+
+import (
+	"bytes"
+	"crypto/md5"
+	"encoding/gob"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"strconv"
+	"time"
+)
+
+// FileCacheItem is basic unit of file cache adapter.
+// it contains data and expire time.
+type FileCacheItem struct {
+	Data       interface{}
+	Lastaccess time.Time
+	Expired    time.Time
+}
+
+// FileCache Config
+var (
+	FileCachePath           = "cache"     // cache directory
+	FileCacheFileSuffix     = ".bin"      // cache file suffix
+	FileCacheDirectoryLevel = 2           // cache file deep level if auto generated cache files.
+	FileCacheEmbedExpiry    time.Duration // cache expire time, default is no expire forever.
+)
+
+// FileCache is cache adapter for file storage.
+type FileCache struct {
+	CachePath      string
+	FileSuffix     string
+	DirectoryLevel int
+	EmbedExpiry    int
+}
+
+// NewFileCache Create new file cache with no config.
+// the level and expiry need set in method StartAndGC as config string.
+func NewFileCache() Cache {
+	//    return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix}
+	return &FileCache{}
+}
+
+// StartAndGC will start and begin gc for file cache.
+// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}
+func (fc *FileCache) StartAndGC(config string) error {
+
+	var cfg map[string]string
+	json.Unmarshal([]byte(config), &cfg)
+	if _, ok := cfg["CachePath"]; !ok {
+		cfg["CachePath"] = FileCachePath
+	}
+	if _, ok := cfg["FileSuffix"]; !ok {
+		cfg["FileSuffix"] = FileCacheFileSuffix
+	}
+	if _, ok := cfg["DirectoryLevel"]; !ok {
+		cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel)
+	}
+	if _, ok := cfg["EmbedExpiry"]; !ok {
+		cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
+	}
+	fc.CachePath = cfg["CachePath"]
+	fc.FileSuffix = cfg["FileSuffix"]
+	fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
+	fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])
+
+	fc.Init()
+	return nil
+}
+
+// Init will make new dir for file cache if not exist.
+func (fc *FileCache) Init() {
+	if ok, _ := exists(fc.CachePath); !ok { // todo : error handle
+		_ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle
+	}
+}
+
+// get cached file name. it's md5 encoded.
+func (fc *FileCache) getCacheFileName(key string) string {
+	m := md5.New()
+	io.WriteString(m, key)
+	keyMd5 := hex.EncodeToString(m.Sum(nil))
+	cachePath := fc.CachePath
+	switch fc.DirectoryLevel {
+	case 2:
+		cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4])
+	case 1:
+		cachePath = filepath.Join(cachePath, keyMd5[0:2])
+	}
+
+	if ok, _ := exists(cachePath); !ok { // todo : error handle
+		_ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle
+	}
+
+	return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix))
+}
+
+// Get value from file cache.
+// if non-exist or expired, return empty string.
+func (fc *FileCache) Get(key string) interface{} {
+	fileData, err := FileGetContents(fc.getCacheFileName(key))
+	if err != nil {
+		return ""
+	}
+	var to FileCacheItem
+	GobDecode(fileData, &to)
+	if to.Expired.Before(time.Now()) {
+		return ""
+	}
+	return to.Data
+}
+
+// GetMulti gets values from file cache.
+// if non-exist or expired, return empty string.
+func (fc *FileCache) GetMulti(keys []string) []interface{} {
+	var rc []interface{}
+	for _, key := range keys {
+		rc = append(rc, fc.Get(key))
+	}
+	return rc
+}
+
+// Put value into file cache.
+// timeout means how long to keep this file, unit of ms.
+// if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever.
+func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error {
+	gob.Register(val)
+
+	item := FileCacheItem{Data: val}
+	if timeout == FileCacheEmbedExpiry {
+		item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years
+	} else {
+		item.Expired = time.Now().Add(timeout)
+	}
+	item.Lastaccess = time.Now()
+	data, err := GobEncode(item)
+	if err != nil {
+		return err
+	}
+	return FilePutContents(fc.getCacheFileName(key), data)
+}
+
+// Delete file cache value.
+func (fc *FileCache) Delete(key string) error {
+	filename := fc.getCacheFileName(key)
+	if ok, _ := exists(filename); ok {
+		return os.Remove(filename)
+	}
+	return nil
+}
+
+// Incr will increase cached int value.
+// fc value is saving forever unless Delete.
+func (fc *FileCache) Incr(key string) error {
+	data := fc.Get(key)
+	var incr int
+	if reflect.TypeOf(data).Name() != "int" {
+		incr = 0
+	} else {
+		incr = data.(int) + 1
+	}
+	fc.Put(key, incr, FileCacheEmbedExpiry)
+	return nil
+}
+
+// Decr will decrease cached int value.
+func (fc *FileCache) Decr(key string) error {
+	data := fc.Get(key)
+	var decr int
+	if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 {
+		decr = 0
+	} else {
+		decr = data.(int) - 1
+	}
+	fc.Put(key, decr, FileCacheEmbedExpiry)
+	return nil
+}
+
+// IsExist check value is exist.
+func (fc *FileCache) IsExist(key string) bool {
+	ret, _ := exists(fc.getCacheFileName(key))
+	return ret
+}
+
+// ClearAll will clean cached files.
+// not implemented.
+func (fc *FileCache) ClearAll() error {
+	return nil
+}
+
+// check file exist.
+func exists(path string) (bool, error) {
+	_, err := os.Stat(path)
+	if err == nil {
+		return true, nil
+	}
+	if os.IsNotExist(err) {
+		return false, nil
+	}
+	return false, err
+}
+
+// FileGetContents Get bytes to file.
+// if non-exist, create this file.
+func FileGetContents(filename string) (data []byte, e error) {
+	return ioutil.ReadFile(filename)
+}
+
+// FilePutContents Put bytes to file.
+// if non-exist, create this file.
+func FilePutContents(filename string, content []byte) error {
+	return ioutil.WriteFile(filename, content, os.ModePerm)
+}
+
+// GobEncode Gob encodes file cache item.
+func GobEncode(data interface{}) ([]byte, error) {
+	buf := bytes.NewBuffer(nil)
+	enc := gob.NewEncoder(buf)
+	err := enc.Encode(data)
+	if err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), err
+}
+
+// GobDecode Gob decodes file cache item.
+func GobDecode(data []byte, to *FileCacheItem) error {
+	buf := bytes.NewBuffer(data)
+	dec := gob.NewDecoder(buf)
+	return dec.Decode(&to)
+}
+
+func init() {
+	Register("file", NewFileCache)
+}

+ 191 - 0
vendor/github.com/fatedier/beego/cache/memcache/memcache.go

@@ -0,0 +1,191 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package memcache for cache provider
+//
+// depend on github.com/bradfitz/gomemcache/memcache
+//
+// go install github.com/bradfitz/gomemcache/memcache
+//
+// Usage:
+// import(
+//   _ "github.com/astaxie/beego/cache/memcache"
+//   "github.com/astaxie/beego/cache"
+// )
+//
+//  bm, err := cache.NewCache("memcache", `{"conn":"127.0.0.1:11211"}`)
+//
+//  more docs http://beego.me/docs/module/cache.md
+package memcache
+
+import (
+	"encoding/json"
+	"errors"
+	"strings"
+	"time"
+
+	"github.com/astaxie/beego/cache"
+	"github.com/bradfitz/gomemcache/memcache"
+)
+
+// Cache Memcache adapter.
+type Cache struct {
+	conn     *memcache.Client
+	conninfo []string
+}
+
+// NewMemCache create new memcache adapter.
+func NewMemCache() cache.Cache {
+	return &Cache{}
+}
+
+// Get get value from memcache.
+func (rc *Cache) Get(key string) interface{} {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	if item, err := rc.conn.Get(key); err == nil {
+		return item.Value
+	}
+	return nil
+}
+
+// GetMulti get value from memcache.
+func (rc *Cache) GetMulti(keys []string) []interface{} {
+	size := len(keys)
+	var rv []interface{}
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			for i := 0; i < size; i++ {
+				rv = append(rv, err)
+			}
+			return rv
+		}
+	}
+	mv, err := rc.conn.GetMulti(keys)
+	if err == nil {
+		for _, v := range mv {
+			rv = append(rv, v.Value)
+		}
+		return rv
+	}
+	for i := 0; i < size; i++ {
+		rv = append(rv, err)
+	}
+	return rv
+}
+
+// Put put value to memcache.
+func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	item := memcache.Item{Key: key, Expiration: int32(timeout / time.Second)}
+	if v, ok := val.([]byte); ok {
+		item.Value = v
+	} else if str, ok := val.(string); ok {
+		item.Value = []byte(str)
+	} else {
+		return errors.New("val only support string and []byte")
+	}
+	return rc.conn.Set(&item)
+}
+
+// Delete delete value in memcache.
+func (rc *Cache) Delete(key string) error {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	return rc.conn.Delete(key)
+}
+
+// Incr increase counter.
+func (rc *Cache) Incr(key string) error {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	_, err := rc.conn.Increment(key, 1)
+	return err
+}
+
+// Decr decrease counter.
+func (rc *Cache) Decr(key string) error {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	_, err := rc.conn.Decrement(key, 1)
+	return err
+}
+
+// IsExist check value exists in memcache.
+func (rc *Cache) IsExist(key string) bool {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return false
+		}
+	}
+	_, err := rc.conn.Get(key)
+	if err != nil {
+		return false
+	}
+	return true
+}
+
+// ClearAll clear all cached in memcache.
+func (rc *Cache) ClearAll() error {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	return rc.conn.FlushAll()
+}
+
+// StartAndGC start memcache adapter.
+// config string is like {"conn":"connection info"}.
+// if connecting error, return.
+func (rc *Cache) StartAndGC(config string) error {
+	var cf map[string]string
+	json.Unmarshal([]byte(config), &cf)
+	if _, ok := cf["conn"]; !ok {
+		return errors.New("config has no conn key")
+	}
+	rc.conninfo = strings.Split(cf["conn"], ";")
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// connect to memcache and keep the connection.
+func (rc *Cache) connectInit() error {
+	rc.conn = memcache.New(rc.conninfo...)
+	return nil
+}
+
+func init() {
+	cache.Register("memcache", NewMemCache)
+}

+ 108 - 0
vendor/github.com/fatedier/beego/cache/memcache/memcache_test.go

@@ -0,0 +1,108 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package memcache
+
+import (
+	_ "github.com/bradfitz/gomemcache/memcache"
+
+	"strconv"
+	"testing"
+	"time"
+
+	"github.com/astaxie/beego/cache"
+)
+
+func TestMemcacheCache(t *testing.T) {
+	bm, err := cache.NewCache("memcache", `{"conn": "127.0.0.1:11211"}`)
+	if err != nil {
+		t.Error("init err")
+	}
+	timeoutDuration := 10 * time.Second
+	if err = bm.Put("astaxie", "1", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if !bm.IsExist("astaxie") {
+		t.Error("check err")
+	}
+
+	time.Sleep(11 * time.Second)
+
+	if bm.IsExist("astaxie") {
+		t.Error("check err")
+	}
+	if err = bm.Put("astaxie", "1", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+
+	if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
+		t.Error("get err")
+	}
+
+	if err = bm.Incr("astaxie"); err != nil {
+		t.Error("Incr Error", err)
+	}
+
+	if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 2 {
+		t.Error("get err")
+	}
+
+	if err = bm.Decr("astaxie"); err != nil {
+		t.Error("Decr Error", err)
+	}
+
+	if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
+		t.Error("get err")
+	}
+	bm.Delete("astaxie")
+	if bm.IsExist("astaxie") {
+		t.Error("delete err")
+	}
+
+	//test string
+	if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if !bm.IsExist("astaxie") {
+		t.Error("check err")
+	}
+
+	if v := bm.Get("astaxie").([]byte); string(v) != "author" {
+		t.Error("get err")
+	}
+
+	//test GetMulti
+	if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if !bm.IsExist("astaxie1") {
+		t.Error("check err")
+	}
+
+	vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
+	if len(vv) != 2 {
+		t.Error("GetMulti ERROR")
+	}
+	if string(vv[0].([]byte)) != "author" && string(vv[0].([]byte)) != "author1" {
+		t.Error("GetMulti ERROR")
+	}
+	if string(vv[1].([]byte)) != "author1" && string(vv[1].([]byte)) != "author" {
+		t.Error("GetMulti ERROR")
+	}
+
+	// test clear all
+	if err = bm.ClearAll(); err != nil {
+		t.Error("clear all err")
+	}
+}

+ 244 - 0
vendor/github.com/fatedier/beego/cache/memory.go

@@ -0,0 +1,244 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cache
+
+import (
+	"encoding/json"
+	"errors"
+	"sync"
+	"time"
+)
+
+var (
+	// DefaultEvery means the clock time of recycling the expired cache items in memory.
+	DefaultEvery = 60 // 1 minute
+)
+
+// MemoryItem store memory cache item.
+type MemoryItem struct {
+	val         interface{}
+	createdTime time.Time
+	lifespan    time.Duration
+}
+
+func (mi *MemoryItem) isExpire() bool {
+	// 0 means forever
+	if mi.lifespan == 0 {
+		return false
+	}
+	return time.Now().Sub(mi.createdTime) > mi.lifespan
+}
+
+// MemoryCache is Memory cache adapter.
+// it contains a RW locker for safe map storage.
+type MemoryCache struct {
+	sync.RWMutex
+	dur   time.Duration
+	items map[string]*MemoryItem
+	Every int // run an expiration check Every clock time
+}
+
+// NewMemoryCache returns a new MemoryCache.
+func NewMemoryCache() Cache {
+	cache := MemoryCache{items: make(map[string]*MemoryItem)}
+	return &cache
+}
+
+// Get cache from memory.
+// if non-existed or expired, return nil.
+func (bc *MemoryCache) Get(name string) interface{} {
+	bc.RLock()
+	defer bc.RUnlock()
+	if itm, ok := bc.items[name]; ok {
+		if itm.isExpire() {
+			return nil
+		}
+		return itm.val
+	}
+	return nil
+}
+
+// GetMulti gets caches from memory.
+// if non-existed or expired, return nil.
+func (bc *MemoryCache) GetMulti(names []string) []interface{} {
+	var rc []interface{}
+	for _, name := range names {
+		rc = append(rc, bc.Get(name))
+	}
+	return rc
+}
+
+// Put cache to memory.
+// if lifespan is 0, it will be forever till restart.
+func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error {
+	bc.Lock()
+	defer bc.Unlock()
+	bc.items[name] = &MemoryItem{
+		val:         value,
+		createdTime: time.Now(),
+		lifespan:    lifespan,
+	}
+	return nil
+}
+
+// Delete cache in memory.
+func (bc *MemoryCache) Delete(name string) error {
+	bc.Lock()
+	defer bc.Unlock()
+	if _, ok := bc.items[name]; !ok {
+		return errors.New("key not exist")
+	}
+	delete(bc.items, name)
+	if _, ok := bc.items[name]; ok {
+		return errors.New("delete key error")
+	}
+	return nil
+}
+
+// Incr increase cache counter in memory.
+// it supports int,int32,int64,uint,uint32,uint64.
+func (bc *MemoryCache) Incr(key string) error {
+	bc.RLock()
+	defer bc.RUnlock()
+	itm, ok := bc.items[key]
+	if !ok {
+		return errors.New("key not exist")
+	}
+	switch itm.val.(type) {
+	case int:
+		itm.val = itm.val.(int) + 1
+	case int32:
+		itm.val = itm.val.(int32) + 1
+	case int64:
+		itm.val = itm.val.(int64) + 1
+	case uint:
+		itm.val = itm.val.(uint) + 1
+	case uint32:
+		itm.val = itm.val.(uint32) + 1
+	case uint64:
+		itm.val = itm.val.(uint64) + 1
+	default:
+		return errors.New("item val is not (u)int (u)int32 (u)int64")
+	}
+	return nil
+}
+
+// Decr decrease counter in memory.
+func (bc *MemoryCache) Decr(key string) error {
+	bc.RLock()
+	defer bc.RUnlock()
+	itm, ok := bc.items[key]
+	if !ok {
+		return errors.New("key not exist")
+	}
+	switch itm.val.(type) {
+	case int:
+		itm.val = itm.val.(int) - 1
+	case int64:
+		itm.val = itm.val.(int64) - 1
+	case int32:
+		itm.val = itm.val.(int32) - 1
+	case uint:
+		if itm.val.(uint) > 0 {
+			itm.val = itm.val.(uint) - 1
+		} else {
+			return errors.New("item val is less than 0")
+		}
+	case uint32:
+		if itm.val.(uint32) > 0 {
+			itm.val = itm.val.(uint32) - 1
+		} else {
+			return errors.New("item val is less than 0")
+		}
+	case uint64:
+		if itm.val.(uint64) > 0 {
+			itm.val = itm.val.(uint64) - 1
+		} else {
+			return errors.New("item val is less than 0")
+		}
+	default:
+		return errors.New("item val is not int int64 int32")
+	}
+	return nil
+}
+
+// IsExist check cache exist in memory.
+func (bc *MemoryCache) IsExist(name string) bool {
+	bc.RLock()
+	defer bc.RUnlock()
+	if v, ok := bc.items[name]; ok {
+		return !v.isExpire()
+	}
+	return false
+}
+
+// ClearAll will delete all cache in memory.
+func (bc *MemoryCache) ClearAll() error {
+	bc.Lock()
+	defer bc.Unlock()
+	bc.items = make(map[string]*MemoryItem)
+	return nil
+}
+
+// StartAndGC start memory cache. it will check expiration in every clock time.
+func (bc *MemoryCache) StartAndGC(config string) error {
+	var cf map[string]int
+	json.Unmarshal([]byte(config), &cf)
+	if _, ok := cf["interval"]; !ok {
+		cf = make(map[string]int)
+		cf["interval"] = DefaultEvery
+	}
+	dur := time.Duration(cf["interval"]) * time.Second
+	bc.Every = cf["interval"]
+	bc.dur = dur
+	go bc.vaccuum()
+	return nil
+}
+
+// check expiration.
+func (bc *MemoryCache) vaccuum() {
+	if bc.Every < 1 {
+		return
+	}
+	for {
+		<-time.After(bc.dur)
+		if bc.items == nil {
+			return
+		}
+		for name := range bc.items {
+			bc.itemExpired(name)
+		}
+	}
+}
+
+// itemExpired returns true if an item is expired.
+func (bc *MemoryCache) itemExpired(name string) bool {
+	bc.Lock()
+	defer bc.Unlock()
+
+	itm, ok := bc.items[name]
+	if !ok {
+		return true
+	}
+	if itm.isExpire() {
+		delete(bc.items, name)
+		return true
+	}
+	return false
+}
+
+func init() {
+	Register("memory", NewMemoryCache)
+}

+ 240 - 0
vendor/github.com/fatedier/beego/cache/redis/redis.go

@@ -0,0 +1,240 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package redis for cache provider
+//
+// depend on github.com/garyburd/redigo/redis
+//
+// go install github.com/garyburd/redigo/redis
+//
+// Usage:
+// import(
+//   _ "github.com/astaxie/beego/cache/redis"
+//   "github.com/astaxie/beego/cache"
+// )
+//
+//  bm, err := cache.NewCache("redis", `{"conn":"127.0.0.1:11211"}`)
+//
+//  more docs http://beego.me/docs/module/cache.md
+package redis
+
+import (
+	"encoding/json"
+	"errors"
+	"strconv"
+	"time"
+
+	"github.com/garyburd/redigo/redis"
+
+	"github.com/astaxie/beego/cache"
+)
+
+var (
+	// DefaultKey the collection name of redis for cache adapter.
+	DefaultKey = "beecacheRedis"
+)
+
+// Cache is Redis cache adapter.
+type Cache struct {
+	p        *redis.Pool // redis connection pool
+	conninfo string
+	dbNum    int
+	key      string
+	password string
+}
+
+// NewRedisCache create new redis cache with default collection name.
+func NewRedisCache() cache.Cache {
+	return &Cache{key: DefaultKey}
+}
+
+// actually do the redis cmds
+func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
+	c := rc.p.Get()
+	defer c.Close()
+
+	return c.Do(commandName, args...)
+}
+
+// Get cache from redis.
+func (rc *Cache) Get(key string) interface{} {
+	if v, err := rc.do("GET", key); err == nil {
+		return v
+	}
+	return nil
+}
+
+// GetMulti get cache from redis.
+func (rc *Cache) GetMulti(keys []string) []interface{} {
+	size := len(keys)
+	var rv []interface{}
+	c := rc.p.Get()
+	defer c.Close()
+	var err error
+	for _, key := range keys {
+		err = c.Send("GET", key)
+		if err != nil {
+			goto ERROR
+		}
+	}
+	if err = c.Flush(); err != nil {
+		goto ERROR
+	}
+	for i := 0; i < size; i++ {
+		if v, err := c.Receive(); err == nil {
+			rv = append(rv, v.([]byte))
+		} else {
+			rv = append(rv, err)
+		}
+	}
+	return rv
+ERROR:
+	rv = rv[0:0]
+	for i := 0; i < size; i++ {
+		rv = append(rv, nil)
+	}
+
+	return rv
+}
+
+// Put put cache to redis.
+func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
+	var err error
+	if _, err = rc.do("SETEX", key, int64(timeout/time.Second), val); err != nil {
+		return err
+	}
+
+	if _, err = rc.do("HSET", rc.key, key, true); err != nil {
+		return err
+	}
+	return err
+}
+
+// Delete delete cache in redis.
+func (rc *Cache) Delete(key string) error {
+	var err error
+	if _, err = rc.do("DEL", key); err != nil {
+		return err
+	}
+	_, err = rc.do("HDEL", rc.key, key)
+	return err
+}
+
+// IsExist check cache's existence in redis.
+func (rc *Cache) IsExist(key string) bool {
+	v, err := redis.Bool(rc.do("EXISTS", key))
+	if err != nil {
+		return false
+	}
+	if v == false {
+		if _, err = rc.do("HDEL", rc.key, key); err != nil {
+			return false
+		}
+	}
+	return v
+}
+
+// Incr increase counter in redis.
+func (rc *Cache) Incr(key string) error {
+	_, err := redis.Bool(rc.do("INCRBY", key, 1))
+	return err
+}
+
+// Decr decrease counter in redis.
+func (rc *Cache) Decr(key string) error {
+	_, err := redis.Bool(rc.do("INCRBY", key, -1))
+	return err
+}
+
+// ClearAll clean all cache in redis. delete this redis collection.
+func (rc *Cache) ClearAll() error {
+	cachedKeys, err := redis.Strings(rc.do("HKEYS", rc.key))
+	if err != nil {
+		return err
+	}
+	for _, str := range cachedKeys {
+		if _, err = rc.do("DEL", str); err != nil {
+			return err
+		}
+	}
+	_, err = rc.do("DEL", rc.key)
+	return err
+}
+
+// StartAndGC start redis cache adapter.
+// config is like {"key":"collection key","conn":"connection info","dbNum":"0"}
+// the cache item in redis are stored forever,
+// so no gc operation.
+func (rc *Cache) StartAndGC(config string) error {
+	var cf map[string]string
+	json.Unmarshal([]byte(config), &cf)
+
+	if _, ok := cf["key"]; !ok {
+		cf["key"] = DefaultKey
+	}
+	if _, ok := cf["conn"]; !ok {
+		return errors.New("config has no conn key")
+	}
+	if _, ok := cf["dbNum"]; !ok {
+		cf["dbNum"] = "0"
+	}
+	if _, ok := cf["password"]; !ok {
+		cf["password"] = ""
+	}
+	rc.key = cf["key"]
+	rc.conninfo = cf["conn"]
+	rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
+	rc.password = cf["password"]
+
+	rc.connectInit()
+
+	c := rc.p.Get()
+	defer c.Close()
+
+	return c.Err()
+}
+
+// connect to redis.
+func (rc *Cache) connectInit() {
+	dialFunc := func() (c redis.Conn, err error) {
+		c, err = redis.Dial("tcp", rc.conninfo)
+		if err != nil {
+			return nil, err
+		}
+
+		if rc.password != "" {
+			if _, err := c.Do("AUTH", rc.password); err != nil {
+				c.Close()
+				return nil, err
+			}
+		}
+
+		_, selecterr := c.Do("SELECT", rc.dbNum)
+		if selecterr != nil {
+			c.Close()
+			return nil, selecterr
+		}
+		return
+	}
+	// initialize a new pool
+	rc.p = &redis.Pool{
+		MaxIdle:     3,
+		IdleTimeout: 180 * time.Second,
+		Dial:        dialFunc,
+	}
+}
+
+func init() {
+	cache.Register("redis", NewRedisCache)
+}

+ 106 - 0
vendor/github.com/fatedier/beego/cache/redis/redis_test.go

@@ -0,0 +1,106 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package redis
+
+import (
+	"testing"
+	"time"
+
+	"github.com/astaxie/beego/cache"
+	"github.com/garyburd/redigo/redis"
+)
+
+func TestRedisCache(t *testing.T) {
+	bm, err := cache.NewCache("redis", `{"conn": "127.0.0.1:6379"}`)
+	if err != nil {
+		t.Error("init err")
+	}
+	timeoutDuration := 10 * time.Second
+	if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if !bm.IsExist("astaxie") {
+		t.Error("check err")
+	}
+
+	time.Sleep(11 * time.Second)
+
+	if bm.IsExist("astaxie") {
+		t.Error("check err")
+	}
+	if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+
+	if v, _ := redis.Int(bm.Get("astaxie"), err); v != 1 {
+		t.Error("get err")
+	}
+
+	if err = bm.Incr("astaxie"); err != nil {
+		t.Error("Incr Error", err)
+	}
+
+	if v, _ := redis.Int(bm.Get("astaxie"), err); v != 2 {
+		t.Error("get err")
+	}
+
+	if err = bm.Decr("astaxie"); err != nil {
+		t.Error("Decr Error", err)
+	}
+
+	if v, _ := redis.Int(bm.Get("astaxie"), err); v != 1 {
+		t.Error("get err")
+	}
+	bm.Delete("astaxie")
+	if bm.IsExist("astaxie") {
+		t.Error("delete err")
+	}
+
+	//test string
+	if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if !bm.IsExist("astaxie") {
+		t.Error("check err")
+	}
+
+	if v, _ := redis.String(bm.Get("astaxie"), err); v != "author" {
+		t.Error("get err")
+	}
+
+	//test GetMulti
+	if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if !bm.IsExist("astaxie1") {
+		t.Error("check err")
+	}
+
+	vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
+	if len(vv) != 2 {
+		t.Error("GetMulti ERROR")
+	}
+	if v, _ := redis.String(vv[0], nil); v != "author" {
+		t.Error("GetMulti ERROR")
+	}
+	if v, _ := redis.String(vv[1], nil); v != "author1" {
+		t.Error("GetMulti ERROR")
+	}
+
+	// test clear all
+	if err = bm.ClearAll(); err != nil {
+		t.Error("clear all err")
+	}
+}

+ 240 - 0
vendor/github.com/fatedier/beego/cache/ssdb/ssdb.go

@@ -0,0 +1,240 @@
+package ssdb
+
+import (
+	"encoding/json"
+	"errors"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/ssdb/gossdb/ssdb"
+
+	"github.com/astaxie/beego/cache"
+)
+
+// Cache SSDB adapter
+type Cache struct {
+	conn     *ssdb.Client
+	conninfo []string
+}
+
+//NewSsdbCache create new ssdb adapter.
+func NewSsdbCache() cache.Cache {
+	return &Cache{}
+}
+
+// Get get value from memcache.
+func (rc *Cache) Get(key string) interface{} {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return nil
+		}
+	}
+	value, err := rc.conn.Get(key)
+	if err == nil {
+		return value
+	}
+	return nil
+}
+
+// GetMulti get value from memcache.
+func (rc *Cache) GetMulti(keys []string) []interface{} {
+	size := len(keys)
+	var values []interface{}
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			for i := 0; i < size; i++ {
+				values = append(values, err)
+			}
+			return values
+		}
+	}
+	res, err := rc.conn.Do("multi_get", keys)
+	resSize := len(res)
+	if err == nil {
+		for i := 1; i < resSize; i += 2 {
+			values = append(values, string(res[i+1]))
+		}
+		return values
+	}
+	for i := 0; i < size; i++ {
+		values = append(values, err)
+	}
+	return values
+}
+
+// DelMulti get value from memcache.
+func (rc *Cache) DelMulti(keys []string) error {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	_, err := rc.conn.Do("multi_del", keys)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// Put put value to memcache. only support string.
+func (rc *Cache) Put(key string, value interface{}, timeout time.Duration) error {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	v, ok := value.(string)
+	if !ok {
+		return errors.New("value must string")
+	}
+	var resp []string
+	var err error
+	ttl := int(timeout / time.Second)
+	if ttl < 0 {
+		resp, err = rc.conn.Do("set", key, v)
+	} else {
+		resp, err = rc.conn.Do("setx", key, v, ttl)
+	}
+	if err != nil {
+		return err
+	}
+	if len(resp) == 2 && resp[0] == "ok" {
+		return nil
+	}
+	return errors.New("bad response")
+}
+
+// Delete delete value in memcache.
+func (rc *Cache) Delete(key string) error {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	_, err := rc.conn.Del(key)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// Incr increase counter.
+func (rc *Cache) Incr(key string) error {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	_, err := rc.conn.Do("incr", key, 1)
+	return err
+}
+
+// Decr decrease counter.
+func (rc *Cache) Decr(key string) error {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	_, err := rc.conn.Do("incr", key, -1)
+	return err
+}
+
+// IsExist check value exists in memcache.
+func (rc *Cache) IsExist(key string) bool {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return false
+		}
+	}
+	resp, err := rc.conn.Do("exists", key)
+	if err != nil {
+		return false
+	}
+	if len(resp) == 2 && resp[1] == "1" {
+		return true
+	}
+	return false
+
+}
+
+// ClearAll clear all cached in memcache.
+func (rc *Cache) ClearAll() error {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	keyStart, keyEnd, limit := "", "", 50
+	resp, err := rc.Scan(keyStart, keyEnd, limit)
+	for err == nil {
+		size := len(resp)
+		if size == 1 {
+			return nil
+		}
+		keys := []string{}
+		for i := 1; i < size; i += 2 {
+			keys = append(keys, string(resp[i]))
+		}
+		_, e := rc.conn.Do("multi_del", keys)
+		if e != nil {
+			return e
+		}
+		keyStart = resp[size-2]
+		resp, err = rc.Scan(keyStart, keyEnd, limit)
+	}
+	return err
+}
+
+// Scan key all cached in ssdb.
+func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, error) {
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return nil, err
+		}
+	}
+	resp, err := rc.conn.Do("scan", keyStart, keyEnd, limit)
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
+// StartAndGC start memcache adapter.
+// config string is like {"conn":"connection info"}.
+// if connecting error, return.
+func (rc *Cache) StartAndGC(config string) error {
+	var cf map[string]string
+	json.Unmarshal([]byte(config), &cf)
+	if _, ok := cf["conn"]; !ok {
+		return errors.New("config has no conn key")
+	}
+	rc.conninfo = strings.Split(cf["conn"], ";")
+	if rc.conn == nil {
+		if err := rc.connectInit(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// connect to memcache and keep the connection.
+func (rc *Cache) connectInit() error {
+	conninfoArray := strings.Split(rc.conninfo[0], ":")
+	host := conninfoArray[0]
+	port, e := strconv.Atoi(conninfoArray[1])
+	if e != nil {
+		return e
+	}
+	var err error
+	rc.conn, err = ssdb.Connect(host, port)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func init() {
+	cache.Register("ssdb", NewSsdbCache)
+}

+ 104 - 0
vendor/github.com/fatedier/beego/cache/ssdb/ssdb_test.go

@@ -0,0 +1,104 @@
+package ssdb
+
+import (
+	"strconv"
+	"testing"
+	"time"
+
+	"github.com/astaxie/beego/cache"
+)
+
+func TestSsdbcacheCache(t *testing.T) {
+	ssdb, err := cache.NewCache("ssdb", `{"conn": "127.0.0.1:8888"}`)
+	if err != nil {
+		t.Error("init err")
+	}
+
+	// test put and exist
+	if ssdb.IsExist("ssdb") {
+		t.Error("check err")
+	}
+	timeoutDuration := 10 * time.Second
+	//timeoutDuration := -10*time.Second   if timeoutDuration is negtive,it means permanent
+	if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if !ssdb.IsExist("ssdb") {
+		t.Error("check err")
+	}
+
+	// Get test done
+	if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+
+	if v := ssdb.Get("ssdb"); v != "ssdb" {
+		t.Error("get Error")
+	}
+
+	//inc/dec test done
+	if err = ssdb.Put("ssdb", "2", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if err = ssdb.Incr("ssdb"); err != nil {
+		t.Error("incr Error", err)
+	}
+
+	if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 {
+		t.Error("get err")
+	}
+
+	if err = ssdb.Decr("ssdb"); err != nil {
+		t.Error("decr error")
+	}
+
+	// test del
+	if err = ssdb.Put("ssdb", "3", timeoutDuration); err != nil {
+		t.Error("set Error", err)
+	}
+	if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 {
+		t.Error("get err")
+	}
+	if err := ssdb.Delete("ssdb"); err == nil {
+		if ssdb.IsExist("ssdb") {
+			t.Error("delete err")
+		}
+	}
+
+	//test string
+	if err = ssdb.Put("ssdb", "ssdb", -10*time.Second); err != nil {
+		t.Error("set Error", err)
+	}
+	if !ssdb.IsExist("ssdb") {
+		t.Error("check err")
+	}
+	if v := ssdb.Get("ssdb").(string); v != "ssdb" {
+		t.Error("get err")
+	}
+
+	//test GetMulti done
+	if err = ssdb.Put("ssdb1", "ssdb1", -10*time.Second); err != nil {
+		t.Error("set Error", err)
+	}
+	if !ssdb.IsExist("ssdb1") {
+		t.Error("check err")
+	}
+	vv := ssdb.GetMulti([]string{"ssdb", "ssdb1"})
+	if len(vv) != 2 {
+		t.Error("getmulti error")
+	}
+	if vv[0].(string) != "ssdb" {
+		t.Error("getmulti error")
+	}
+	if vv[1].(string) != "ssdb1" {
+		t.Error("getmulti error")
+	}
+
+	// test clear all done
+	if err = ssdb.ClearAll(); err != nil {
+		t.Error("clear all err")
+	}
+	if ssdb.IsExist("ssdb") || ssdb.IsExist("ssdb1") {
+		t.Error("check err")
+	}
+}

+ 489 - 0
vendor/github.com/fatedier/beego/config.go

@@ -0,0 +1,489 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package beego
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"reflect"
+	"runtime"
+	"strings"
+
+	"github.com/astaxie/beego/config"
+	"github.com/astaxie/beego/context"
+	"github.com/astaxie/beego/logs"
+	"github.com/astaxie/beego/session"
+	"github.com/astaxie/beego/utils"
+)
+
+// Config is the main struct for BConfig
+type Config struct {
+	AppName             string //Application name
+	RunMode             string //Running Mode: dev | prod
+	RouterCaseSensitive bool
+	ServerName          string
+	RecoverPanic        bool
+	RecoverFunc         func(*context.Context)
+	CopyRequestBody     bool
+	EnableGzip          bool
+	MaxMemory           int64
+	EnableErrorsShow    bool
+	EnableErrorsRender  bool
+	Listen              Listen
+	WebConfig           WebConfig
+	Log                 LogConfig
+}
+
+// Listen holds for http and https related config
+type Listen struct {
+	Graceful      bool // Graceful means use graceful module to start the server
+	ServerTimeOut int64
+	ListenTCP4    bool
+	EnableHTTP    bool
+	HTTPAddr      string
+	HTTPPort      int
+	EnableHTTPS   bool
+	HTTPSAddr     string
+	HTTPSPort     int
+	HTTPSCertFile string
+	HTTPSKeyFile  string
+	EnableAdmin   bool
+	AdminAddr     string
+	AdminPort     int
+	EnableFcgi    bool
+	EnableStdIo   bool // EnableStdIo works with EnableFcgi Use FCGI via standard I/O
+}
+
+// WebConfig holds web related config
+type WebConfig struct {
+	AutoRender             bool
+	EnableDocs             bool
+	FlashName              string
+	FlashSeparator         string
+	DirectoryIndex         bool
+	StaticDir              map[string]string
+	StaticExtensionsToGzip []string
+	TemplateLeft           string
+	TemplateRight          string
+	ViewsPath              string
+	EnableXSRF             bool
+	XSRFKey                string
+	XSRFExpire             int
+	Session                SessionConfig
+}
+
+// SessionConfig holds session related config
+type SessionConfig struct {
+	SessionOn                    bool
+	SessionProvider              string
+	SessionName                  string
+	SessionGCMaxLifetime         int64
+	SessionProviderConfig        string
+	SessionCookieLifeTime        int
+	SessionAutoSetCookie         bool
+	SessionDomain                string
+	SessionDisableHTTPOnly       bool // used to allow for cross domain cookies/javascript cookies.
+	SessionEnableSidInHTTPHeader bool //	enable store/get the sessionId into/from http headers
+	SessionNameInHTTPHeader      string
+	SessionEnableSidInURLQuery   bool //	enable get the sessionId from Url Query params
+}
+
+// LogConfig holds Log related config
+type LogConfig struct {
+	AccessLogs  bool
+	FileLineNum bool
+	Outputs     map[string]string // Store Adaptor : config
+}
+
+var (
+	// BConfig is the default config for Application
+	BConfig *Config
+	// AppConfig is the instance of Config, store the config information from file
+	AppConfig *beegoAppConfig
+	// AppPath is the absolute path to the app
+	AppPath string
+	// GlobalSessions is the instance for the session manager
+	GlobalSessions *session.Manager
+
+	// appConfigPath is the path to the config files
+	appConfigPath string
+	// appConfigProvider is the provider for the config, default is ini
+	appConfigProvider = "ini"
+)
+
+func init() {
+	BConfig = newBConfig()
+	var err error
+	if AppPath, err = filepath.Abs(filepath.Dir(os.Args[0])); err != nil {
+		panic(err)
+	}
+	workPath, err := os.Getwd()
+	if err != nil {
+		panic(err)
+	}
+	appConfigPath = filepath.Join(workPath, "conf", "app.conf")
+	if !utils.FileExists(appConfigPath) {
+		appConfigPath = filepath.Join(AppPath, "conf", "app.conf")
+		if !utils.FileExists(appConfigPath) {
+			AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()}
+			return
+		}
+	}
+	if err = parseConfig(appConfigPath); err != nil {
+		panic(err)
+	}
+}
+
+func recoverPanic(ctx *context.Context) {
+	if err := recover(); err != nil {
+		if err == ErrAbort {
+			return
+		}
+		if !BConfig.RecoverPanic {
+			panic(err)
+		}
+		if BConfig.EnableErrorsShow {
+			if _, ok := ErrorMaps[fmt.Sprint(err)]; ok {
+				exception(fmt.Sprint(err), ctx)
+				return
+			}
+		}
+		var stack string
+		logs.Critical("the request url is ", ctx.Input.URL())
+		logs.Critical("Handler crashed with error", err)
+		for i := 1; ; i++ {
+			_, file, line, ok := runtime.Caller(i)
+			if !ok {
+				break
+			}
+			logs.Critical(fmt.Sprintf("%s:%d", file, line))
+			stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
+		}
+		if BConfig.RunMode == DEV && BConfig.EnableErrorsRender {
+			showErr(err, ctx, stack)
+		}
+	}
+}
+
+func newBConfig() *Config {
+	return &Config{
+		AppName:             "beego",
+		RunMode:             DEV,
+		RouterCaseSensitive: true,
+		ServerName:          "beegoServer:" + VERSION,
+		RecoverPanic:        true,
+		RecoverFunc:         recoverPanic,
+		CopyRequestBody:     false,
+		EnableGzip:          false,
+		MaxMemory:           1 << 26, //64MB
+		EnableErrorsShow:    true,
+		EnableErrorsRender:  true,
+		Listen: Listen{
+			Graceful:      false,
+			ServerTimeOut: 0,
+			ListenTCP4:    false,
+			EnableHTTP:    true,
+			HTTPAddr:      "",
+			HTTPPort:      8080,
+			EnableHTTPS:   false,
+			HTTPSAddr:     "",
+			HTTPSPort:     10443,
+			HTTPSCertFile: "",
+			HTTPSKeyFile:  "",
+			EnableAdmin:   false,
+			AdminAddr:     "",
+			AdminPort:     8088,
+			EnableFcgi:    false,
+			EnableStdIo:   false,
+		},
+		WebConfig: WebConfig{
+			AutoRender:             true,
+			EnableDocs:             false,
+			FlashName:              "BEEGO_FLASH",
+			FlashSeparator:         "BEEGOFLASH",
+			DirectoryIndex:         false,
+			StaticDir:              map[string]string{"/static": "static"},
+			StaticExtensionsToGzip: []string{".css", ".js"},
+			TemplateLeft:           "{{",
+			TemplateRight:          "}}",
+			ViewsPath:              "views",
+			EnableXSRF:             false,
+			XSRFKey:                "beegoxsrf",
+			XSRFExpire:             0,
+			Session: SessionConfig{
+				SessionOn:                    false,
+				SessionProvider:              "memory",
+				SessionName:                  "beegosessionID",
+				SessionGCMaxLifetime:         3600,
+				SessionProviderConfig:        "",
+				SessionDisableHTTPOnly:       false,
+				SessionCookieLifeTime:        0, //set cookie default is the browser life
+				SessionAutoSetCookie:         true,
+				SessionDomain:                "",
+				SessionEnableSidInHTTPHeader: false, //	enable store/get the sessionId into/from http headers
+				SessionNameInHTTPHeader:      "Beegosessionid",
+				SessionEnableSidInURLQuery:   false, //	enable get the sessionId from Url Query params
+			},
+		},
+		Log: LogConfig{
+			AccessLogs:  false,
+			FileLineNum: true,
+			Outputs:     map[string]string{"console": ""},
+		},
+	}
+}
+
+// now only support ini, next will support json.
+func parseConfig(appConfigPath string) (err error) {
+	AppConfig, err = newAppConfig(appConfigProvider, appConfigPath)
+	if err != nil {
+		return err
+	}
+	return assignConfig(AppConfig)
+}
+
+func assignConfig(ac config.Configer) error {
+	for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} {
+		assignSingleConfig(i, ac)
+	}
+	// set the run mode first
+	if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" {
+		BConfig.RunMode = envRunMode
+	} else if runMode := ac.String("RunMode"); runMode != "" {
+		BConfig.RunMode = runMode
+	}
+
+	if sd := ac.String("StaticDir"); sd != "" {
+		BConfig.WebConfig.StaticDir = map[string]string{}
+		sds := strings.Fields(sd)
+		for _, v := range sds {
+			if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 {
+				BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[1]
+			} else {
+				BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[0]
+			}
+		}
+	}
+
+	if sgz := ac.String("StaticExtensionsToGzip"); sgz != "" {
+		extensions := strings.Split(sgz, ",")
+		fileExts := []string{}
+		for _, ext := range extensions {
+			ext = strings.TrimSpace(ext)
+			if ext == "" {
+				continue
+			}
+			if !strings.HasPrefix(ext, ".") {
+				ext = "." + ext
+			}
+			fileExts = append(fileExts, ext)
+		}
+		if len(fileExts) > 0 {
+			BConfig.WebConfig.StaticExtensionsToGzip = fileExts
+		}
+	}
+
+	if lo := ac.String("LogOutputs"); lo != "" {
+		// if lo is not nil or empty
+		// means user has set his own LogOutputs
+		// clear the default setting to BConfig.Log.Outputs
+		BConfig.Log.Outputs = make(map[string]string)
+		los := strings.Split(lo, ";")
+		for _, v := range los {
+			if logType2Config := strings.SplitN(v, ",", 2); len(logType2Config) == 2 {
+				BConfig.Log.Outputs[logType2Config[0]] = logType2Config[1]
+			} else {
+				continue
+			}
+		}
+	}
+
+	//init log
+	logs.Reset()
+	for adaptor, config := range BConfig.Log.Outputs {
+		err := logs.SetLogger(adaptor, config)
+		if err != nil {
+			fmt.Fprintln(os.Stderr, fmt.Sprintf("%s with the config %q got err:%s", adaptor, config, err.Error()))
+		}
+	}
+	logs.SetLogFuncCall(BConfig.Log.FileLineNum)
+
+	return nil
+}
+
+func assignSingleConfig(p interface{}, ac config.Configer) {
+	pt := reflect.TypeOf(p)
+	if pt.Kind() != reflect.Ptr {
+		return
+	}
+	pt = pt.Elem()
+	if pt.Kind() != reflect.Struct {
+		return
+	}
+	pv := reflect.ValueOf(p).Elem()
+
+	for i := 0; i < pt.NumField(); i++ {
+		pf := pv.Field(i)
+		if !pf.CanSet() {
+			continue
+		}
+		name := pt.Field(i).Name
+		switch pf.Kind() {
+		case reflect.String:
+			pf.SetString(ac.DefaultString(name, pf.String()))
+		case reflect.Int, reflect.Int64:
+			pf.SetInt(int64(ac.DefaultInt64(name, pf.Int())))
+		case reflect.Bool:
+			pf.SetBool(ac.DefaultBool(name, pf.Bool()))
+		case reflect.Struct:
+		default:
+			//do nothing here
+		}
+	}
+
+}
+
+// LoadAppConfig allow developer to apply a config file
+func LoadAppConfig(adapterName, configPath string) error {
+	absConfigPath, err := filepath.Abs(configPath)
+	if err != nil {
+		return err
+	}
+
+	if !utils.FileExists(absConfigPath) {
+		return fmt.Errorf("the target config file: %s don't exist", configPath)
+	}
+
+	appConfigPath = absConfigPath
+	appConfigProvider = adapterName
+
+	return parseConfig(appConfigPath)
+}
+
+type beegoAppConfig struct {
+	innerConfig config.Configer
+}
+
+func newAppConfig(appConfigProvider, appConfigPath string) (*beegoAppConfig, error) {
+	ac, err := config.NewConfig(appConfigProvider, appConfigPath)
+	if err != nil {
+		return nil, err
+	}
+	return &beegoAppConfig{ac}, nil
+}
+
+func (b *beegoAppConfig) Set(key, val string) error {
+	if err := b.innerConfig.Set(BConfig.RunMode+"::"+key, val); err != nil {
+		return err
+	}
+	return b.innerConfig.Set(key, val)
+}
+
+func (b *beegoAppConfig) String(key string) string {
+	if v := b.innerConfig.String(BConfig.RunMode + "::" + key); v != "" {
+		return v
+	}
+	return b.innerConfig.String(key)
+}
+
+func (b *beegoAppConfig) Strings(key string) []string {
+	if v := b.innerConfig.Strings(BConfig.RunMode + "::" + key); len(v) > 0 {
+		return v
+	}
+	return b.innerConfig.Strings(key)
+}
+
+func (b *beegoAppConfig) Int(key string) (int, error) {
+	if v, err := b.innerConfig.Int(BConfig.RunMode + "::" + key); err == nil {
+		return v, nil
+	}
+	return b.innerConfig.Int(key)
+}
+
+func (b *beegoAppConfig) Int64(key string) (int64, error) {
+	if v, err := b.innerConfig.Int64(BConfig.RunMode + "::" + key); err == nil {
+		return v, nil
+	}
+	return b.innerConfig.Int64(key)
+}
+
+func (b *beegoAppConfig) Bool(key string) (bool, error) {
+	if v, err := b.innerConfig.Bool(BConfig.RunMode + "::" + key); err == nil {
+		return v, nil
+	}
+	return b.innerConfig.Bool(key)
+}
+
+func (b *beegoAppConfig) Float(key string) (float64, error) {
+	if v, err := b.innerConfig.Float(BConfig.RunMode + "::" + key); err == nil {
+		return v, nil
+	}
+	return b.innerConfig.Float(key)
+}
+
+func (b *beegoAppConfig) DefaultString(key string, defaultVal string) string {
+	if v := b.String(key); v != "" {
+		return v
+	}
+	return defaultVal
+}
+
+func (b *beegoAppConfig) DefaultStrings(key string, defaultVal []string) []string {
+	if v := b.Strings(key); len(v) != 0 {
+		return v
+	}
+	return defaultVal
+}
+
+func (b *beegoAppConfig) DefaultInt(key string, defaultVal int) int {
+	if v, err := b.Int(key); err == nil {
+		return v
+	}
+	return defaultVal
+}
+
+func (b *beegoAppConfig) DefaultInt64(key string, defaultVal int64) int64 {
+	if v, err := b.Int64(key); err == nil {
+		return v
+	}
+	return defaultVal
+}
+
+func (b *beegoAppConfig) DefaultBool(key string, defaultVal bool) bool {
+	if v, err := b.Bool(key); err == nil {
+		return v
+	}
+	return defaultVal
+}
+
+func (b *beegoAppConfig) DefaultFloat(key string, defaultVal float64) float64 {
+	if v, err := b.Float(key); err == nil {
+		return v
+	}
+	return defaultVal
+}
+
+func (b *beegoAppConfig) DIY(key string) (interface{}, error) {
+	return b.innerConfig.DIY(key)
+}
+
+func (b *beegoAppConfig) GetSection(section string) (map[string]string, error) {
+	return b.innerConfig.GetSection(section)
+}
+
+func (b *beegoAppConfig) SaveConfigFile(filename string) error {
+	return b.innerConfig.SaveConfigFile(filename)
+}

+ 242 - 0
vendor/github.com/fatedier/beego/config/config.go

@@ -0,0 +1,242 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package config is used to parse config.
+// Usage:
+//  import "github.com/astaxie/beego/config"
+//Examples.
+//
+//  cnf, err := config.NewConfig("ini", "config.conf")
+//
+//  cnf APIS:
+//
+//  cnf.Set(key, val string) error
+//  cnf.String(key string) string
+//  cnf.Strings(key string) []string
+//  cnf.Int(key string) (int, error)
+//  cnf.Int64(key string) (int64, error)
+//  cnf.Bool(key string) (bool, error)
+//  cnf.Float(key string) (float64, error)
+//  cnf.DefaultString(key string, defaultVal string) string
+//  cnf.DefaultStrings(key string, defaultVal []string) []string
+//  cnf.DefaultInt(key string, defaultVal int) int
+//  cnf.DefaultInt64(key string, defaultVal int64) int64
+//  cnf.DefaultBool(key string, defaultVal bool) bool
+//  cnf.DefaultFloat(key string, defaultVal float64) float64
+//  cnf.DIY(key string) (interface{}, error)
+//  cnf.GetSection(section string) (map[string]string, error)
+//  cnf.SaveConfigFile(filename string) error
+//More docs http://beego.me/docs/module/config.md
+package config
+
+import (
+	"fmt"
+	"os"
+	"reflect"
+	"time"
+)
+
+// Configer defines how to get and set value from configuration raw data.
+type Configer interface {
+	Set(key, val string) error   //support section::key type in given key when using ini type.
+	String(key string) string    //support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
+	Strings(key string) []string //get string slice
+	Int(key string) (int, error)
+	Int64(key string) (int64, error)
+	Bool(key string) (bool, error)
+	Float(key string) (float64, error)
+	DefaultString(key string, defaultVal string) string      // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
+	DefaultStrings(key string, defaultVal []string) []string //get string slice
+	DefaultInt(key string, defaultVal int) int
+	DefaultInt64(key string, defaultVal int64) int64
+	DefaultBool(key string, defaultVal bool) bool
+	DefaultFloat(key string, defaultVal float64) float64
+	DIY(key string) (interface{}, error)
+	GetSection(section string) (map[string]string, error)
+	SaveConfigFile(filename string) error
+}
+
+// Config is the adapter interface for parsing config file to get raw data to Configer.
+type Config interface {
+	Parse(key string) (Configer, error)
+	ParseData(data []byte) (Configer, error)
+}
+
+var adapters = make(map[string]Config)
+
+// Register makes a config adapter available by the adapter name.
+// If Register is called twice with the same name or if driver is nil,
+// it panics.
+func Register(name string, adapter Config) {
+	if adapter == nil {
+		panic("config: Register adapter is nil")
+	}
+	if _, ok := adapters[name]; ok {
+		panic("config: Register called twice for adapter " + name)
+	}
+	adapters[name] = adapter
+}
+
+// NewConfig adapterName is ini/json/xml/yaml.
+// filename is the config file path.
+func NewConfig(adapterName, filename string) (Configer, error) {
+	adapter, ok := adapters[adapterName]
+	if !ok {
+		return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName)
+	}
+	return adapter.Parse(filename)
+}
+
+// NewConfigData adapterName is ini/json/xml/yaml.
+// data is the config data.
+func NewConfigData(adapterName string, data []byte) (Configer, error) {
+	adapter, ok := adapters[adapterName]
+	if !ok {
+		return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName)
+	}
+	return adapter.ParseData(data)
+}
+
+// ExpandValueEnvForMap convert all string value with environment variable.
+func ExpandValueEnvForMap(m map[string]interface{}) map[string]interface{} {
+	for k, v := range m {
+		switch value := v.(type) {
+		case string:
+			m[k] = ExpandValueEnv(value)
+		case map[string]interface{}:
+			m[k] = ExpandValueEnvForMap(value)
+		case map[string]string:
+			for k2, v2 := range value {
+				value[k2] = ExpandValueEnv(v2)
+			}
+			m[k] = value
+		}
+	}
+	return m
+}
+
+// ExpandValueEnv returns value of convert with environment variable.
+//
+// Return environment variable if value start with "${" and end with "}".
+// Return default value if environment variable is empty or not exist.
+//
+// It accept value formats "${env}" , "${env||}}" , "${env||defaultValue}" , "defaultvalue".
+// Examples:
+//	v1 := config.ExpandValueEnv("${GOPATH}")			// return the GOPATH environment variable.
+//	v2 := config.ExpandValueEnv("${GOAsta||/usr/local/go}")	// return the default value "/usr/local/go/".
+//	v3 := config.ExpandValueEnv("Astaxie")				// return the value "Astaxie".
+func ExpandValueEnv(value string) (realValue string) {
+	realValue = value
+
+	vLen := len(value)
+	// 3 = ${}
+	if vLen < 3 {
+		return
+	}
+	// Need start with "${" and end with "}", then return.
+	if value[0] != '$' || value[1] != '{' || value[vLen-1] != '}' {
+		return
+	}
+
+	key := ""
+	defalutV := ""
+	// value start with "${"
+	for i := 2; i < vLen; i++ {
+		if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
+			key = value[2:i]
+			defalutV = value[i+2 : vLen-1] // other string is default value.
+			break
+		} else if value[i] == '}' {
+			key = value[2:i]
+			break
+		}
+	}
+
+	realValue = os.Getenv(key)
+	if realValue == "" {
+		realValue = defalutV
+	}
+
+	return
+}
+
+// ParseBool returns the boolean value represented by the string.
+//
+// It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On,
+// 0, 0.0, f, F, FALSE, false, False, NO, no, No, N,n, OFF, off, Off.
+// Any other value returns an error.
+func ParseBool(val interface{}) (value bool, err error) {
+	if val != nil {
+		switch v := val.(type) {
+		case bool:
+			return v, nil
+		case string:
+			switch v {
+			case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "Y", "y", "ON", "on", "On":
+				return true, nil
+			case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "N", "n", "OFF", "off", "Off":
+				return false, nil
+			}
+		case int8, int32, int64:
+			strV := fmt.Sprintf("%s", v)
+			if strV == "1" {
+				return true, nil
+			} else if strV == "0" {
+				return false, nil
+			}
+		case float64:
+			if v == 1 {
+				return true, nil
+			} else if v == 0 {
+				return false, nil
+			}
+		}
+		return false, fmt.Errorf("parsing %q: invalid syntax", val)
+	}
+	return false, fmt.Errorf("parsing <nil>: invalid syntax")
+}
+
+// ToString converts values of any type to string.
+func ToString(x interface{}) string {
+	switch y := x.(type) {
+
+	// Handle dates with special logic
+	// This needs to come above the fmt.Stringer
+	// test since time.Time's have a .String()
+	// method
+	case time.Time:
+		return y.Format("A Monday")
+
+	// Handle type string
+	case string:
+		return y
+
+	// Handle type with .String() method
+	case fmt.Stringer:
+		return y.String()
+
+	// Handle type with .Error() method
+	case error:
+		return y.Error()
+
+	}
+
+	// Handle named string type
+	if v := reflect.ValueOf(x); v.Kind() == reflect.String {
+		return v.String()
+	}
+
+	// Fallback to fmt package for anything else like numeric types
+	return fmt.Sprint(x)
+}

+ 55 - 0
vendor/github.com/fatedier/beego/config/config_test.go

@@ -0,0 +1,55 @@
+// Copyright 2016 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+	"os"
+	"testing"
+)
+
+func TestExpandValueEnv(t *testing.T) {
+
+	testCases := []struct {
+		item string
+		want string
+	}{
+		{"", ""},
+		{"$", "$"},
+		{"{", "{"},
+		{"{}", "{}"},
+		{"${}", ""},
+		{"${|}", ""},
+		{"${}", ""},
+		{"${{}}", ""},
+		{"${{||}}", "}"},
+		{"${pwd||}", ""},
+		{"${pwd||}", ""},
+		{"${pwd||}", ""},
+		{"${pwd||}}", "}"},
+		{"${pwd||{{||}}}", "{{||}}"},
+		{"${GOPATH}", os.Getenv("GOPATH")},
+		{"${GOPATH||}", os.Getenv("GOPATH")},
+		{"${GOPATH||root}", os.Getenv("GOPATH")},
+		{"${GOPATH_NOT||root}", "root"},
+		{"${GOPATH_NOT||||root}", "||root"},
+	}
+
+	for _, c := range testCases {
+		if got := ExpandValueEnv(c.item); got != c.want {
+			t.Errorf("expand value error, item %q want %q, got %q", c.item, c.want, got)
+		}
+	}
+
+}

+ 85 - 0
vendor/github.com/fatedier/beego/config/env/env.go

@@ -0,0 +1,85 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+// Copyright 2017 Faissal Elamraoui. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package env
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/astaxie/beego/utils"
+)
+
+var env *utils.BeeMap
+
+func init() {
+	env = utils.NewBeeMap()
+	for _, e := range os.Environ() {
+		splits := strings.Split(e, "=")
+		env.Set(splits[0], os.Getenv(splits[0]))
+	}
+}
+
+// Get returns a value by key.
+// If the key does not exist, the default value will be returned.
+func Get(key string, defVal string) string {
+	if val := env.Get(key); val != nil {
+		return val.(string)
+	}
+	return defVal
+}
+
+// MustGet returns a value by key.
+// If the key does not exist, it will return an error.
+func MustGet(key string) (string, error) {
+	if val := env.Get(key); val != nil {
+		return val.(string), nil
+	}
+	return "", fmt.Errorf("no env variable with %s", key)
+}
+
+// Set sets a value in the ENV copy.
+// This does not affect the child process environment.
+func Set(key string, value string) {
+	env.Set(key, value)
+}
+
+// MustSet sets a value in the ENV copy and the child process environment.
+// It returns an error in case the set operation failed.
+func MustSet(key string, value string) error {
+	err := os.Setenv(key, value)
+	if err != nil {
+		return err
+	}
+	env.Set(key, value)
+	return nil
+}
+
+// GetAll returns all keys/values in the current child process environment.
+func GetAll() map[string]string {
+	items := env.Items()
+	envs := make(map[string]string, env.Count())
+
+	for key, val := range items {
+		switch key := key.(type) {
+		case string:
+			switch val := val.(type) {
+			case string:
+				envs[key] = val
+			}
+		}
+	}
+	return envs
+}

+ 75 - 0
vendor/github.com/fatedier/beego/config/env/env_test.go

@@ -0,0 +1,75 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+// Copyright 2017 Faissal Elamraoui. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package env
+
+import (
+	"os"
+	"testing"
+)
+
+func TestEnvGet(t *testing.T) {
+	gopath := Get("GOPATH", "")
+	if gopath != os.Getenv("GOPATH") {
+		t.Error("expected GOPATH not empty.")
+	}
+
+	noExistVar := Get("NOEXISTVAR", "foo")
+	if noExistVar != "foo" {
+		t.Errorf("expected NOEXISTVAR to equal foo, got %s.", noExistVar)
+	}
+}
+
+func TestEnvMustGet(t *testing.T) {
+	gopath, err := MustGet("GOPATH")
+	if err != nil {
+		t.Error(err)
+	}
+
+	if gopath != os.Getenv("GOPATH") {
+		t.Errorf("expected GOPATH to be the same, got %s.", gopath)
+	}
+
+	_, err = MustGet("NOEXISTVAR")
+	if err == nil {
+		t.Error("expected error to be non-nil")
+	}
+}
+
+func TestEnvSet(t *testing.T) {
+	Set("MYVAR", "foo")
+	myVar := Get("MYVAR", "bar")
+	if myVar != "foo" {
+		t.Errorf("expected MYVAR to equal foo, got %s.", myVar)
+	}
+}
+
+func TestEnvMustSet(t *testing.T) {
+	err := MustSet("FOO", "bar")
+	if err != nil {
+		t.Error(err)
+	}
+
+	fooVar := os.Getenv("FOO")
+	if fooVar != "bar" {
+		t.Errorf("expected FOO variable to equal bar, got %s.", fooVar)
+	}
+}
+
+func TestEnvGetAll(t *testing.T) {
+	envMap := GetAll()
+	if len(envMap) == 0 {
+		t.Error("expected environment not empty.")
+	}
+}

+ 134 - 0
vendor/github.com/fatedier/beego/config/fake.go

@@ -0,0 +1,134 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+	"errors"
+	"strconv"
+	"strings"
+)
+
+type fakeConfigContainer struct {
+	data map[string]string
+}
+
+func (c *fakeConfigContainer) getData(key string) string {
+	return c.data[strings.ToLower(key)]
+}
+
+func (c *fakeConfigContainer) Set(key, val string) error {
+	c.data[strings.ToLower(key)] = val
+	return nil
+}
+
+func (c *fakeConfigContainer) String(key string) string {
+	return c.getData(key)
+}
+
+func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string {
+	v := c.String(key)
+	if v == "" {
+		return defaultval
+	}
+	return v
+}
+
+func (c *fakeConfigContainer) Strings(key string) []string {
+	v := c.String(key)
+	if v == "" {
+		return nil
+	}
+	return strings.Split(v, ";")
+}
+
+func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string {
+	v := c.Strings(key)
+	if v == nil {
+		return defaultval
+	}
+	return v
+}
+
+func (c *fakeConfigContainer) Int(key string) (int, error) {
+	return strconv.Atoi(c.getData(key))
+}
+
+func (c *fakeConfigContainer) DefaultInt(key string, defaultval int) int {
+	v, err := c.Int(key)
+	if err != nil {
+		return defaultval
+	}
+	return v
+}
+
+func (c *fakeConfigContainer) Int64(key string) (int64, error) {
+	return strconv.ParseInt(c.getData(key), 10, 64)
+}
+
+func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
+	v, err := c.Int64(key)
+	if err != nil {
+		return defaultval
+	}
+	return v
+}
+
+func (c *fakeConfigContainer) Bool(key string) (bool, error) {
+	return ParseBool(c.getData(key))
+}
+
+func (c *fakeConfigContainer) DefaultBool(key string, defaultval bool) bool {
+	v, err := c.Bool(key)
+	if err != nil {
+		return defaultval
+	}
+	return v
+}
+
+func (c *fakeConfigContainer) Float(key string) (float64, error) {
+	return strconv.ParseFloat(c.getData(key), 64)
+}
+
+func (c *fakeConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
+	v, err := c.Float(key)
+	if err != nil {
+		return defaultval
+	}
+	return v
+}
+
+func (c *fakeConfigContainer) DIY(key string) (interface{}, error) {
+	if v, ok := c.data[strings.ToLower(key)]; ok {
+		return v, nil
+	}
+	return nil, errors.New("key not find")
+}
+
+func (c *fakeConfigContainer) GetSection(section string) (map[string]string, error) {
+	return nil, errors.New("not implement in the fakeConfigContainer")
+}
+
+func (c *fakeConfigContainer) SaveConfigFile(filename string) error {
+	return errors.New("not implement in the fakeConfigContainer")
+}
+
+var _ Configer = new(fakeConfigContainer)
+
+// NewFakeConfig return a fake Congiger
+func NewFakeConfig() Configer {
+	return &fakeConfigContainer{
+		data: make(map[string]string),
+	}
+}

+ 474 - 0
vendor/github.com/fatedier/beego/config/ini.go

@@ -0,0 +1,474 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+	"bufio"
+	"bytes"
+	"errors"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"sync"
+)
+
+var (
+	defaultSection = "default"   // default section means if some ini items not in a section, make them in default section,
+	bNumComment    = []byte{'#'} // number signal
+	bSemComment    = []byte{';'} // semicolon signal
+	bEmpty         = []byte{}
+	bEqual         = []byte{'='} // equal signal
+	bDQuote        = []byte{'"'} // quote signal
+	sectionStart   = []byte{'['} // section start signal
+	sectionEnd     = []byte{']'} // section end signal
+	lineBreak      = "\n"
+)
+
+// IniConfig implements Config to parse ini file.
+type IniConfig struct {
+}
+
+// Parse creates a new Config and parses the file configuration from the named file.
+func (ini *IniConfig) Parse(name string) (Configer, error) {
+	return ini.parseFile(name)
+}
+
+func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
+	data, err := ioutil.ReadFile(name)
+	if err != nil {
+		return nil, err
+	}
+
+	return ini.parseData(filepath.Dir(name), data)
+}
+
+func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, error) {
+	cfg := &IniConfigContainer{
+		data:           make(map[string]map[string]string),
+		sectionComment: make(map[string]string),
+		keyComment:     make(map[string]string),
+		RWMutex:        sync.RWMutex{},
+	}
+	cfg.Lock()
+	defer cfg.Unlock()
+
+	var comment bytes.Buffer
+	buf := bufio.NewReader(bytes.NewBuffer(data))
+	// check the BOM
+	head, err := buf.Peek(3)
+	if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 {
+		for i := 1; i <= 3; i++ {
+			buf.ReadByte()
+		}
+	}
+	section := defaultSection
+	for {
+		line, _, err := buf.ReadLine()
+		if err == io.EOF {
+			break
+		}
+		//It might be a good idea to throw a error on all unknonw errors?
+		if _, ok := err.(*os.PathError); ok {
+			return nil, err
+		}
+		line = bytes.TrimSpace(line)
+		if bytes.Equal(line, bEmpty) {
+			continue
+		}
+		var bComment []byte
+		switch {
+		case bytes.HasPrefix(line, bNumComment):
+			bComment = bNumComment
+		case bytes.HasPrefix(line, bSemComment):
+			bComment = bSemComment
+		}
+		if bComment != nil {
+			line = bytes.TrimLeft(line, string(bComment))
+			// Need append to a new line if multi-line comments.
+			if comment.Len() > 0 {
+				comment.WriteByte('\n')
+			}
+			comment.Write(line)
+			continue
+		}
+
+		if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) {
+			section = strings.ToLower(string(line[1 : len(line)-1])) // section name case insensitive
+			if comment.Len() > 0 {
+				cfg.sectionComment[section] = comment.String()
+				comment.Reset()
+			}
+			if _, ok := cfg.data[section]; !ok {
+				cfg.data[section] = make(map[string]string)
+			}
+			continue
+		}
+
+		if _, ok := cfg.data[section]; !ok {
+			cfg.data[section] = make(map[string]string)
+		}
+		keyValue := bytes.SplitN(line, bEqual, 2)
+
+		key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive
+		key = strings.ToLower(key)
+
+		// handle include "other.conf"
+		if len(keyValue) == 1 && strings.HasPrefix(key, "include") {
+
+			includefiles := strings.Fields(key)
+			if includefiles[0] == "include" && len(includefiles) == 2 {
+
+				otherfile := strings.Trim(includefiles[1], "\"")
+				if !filepath.IsAbs(otherfile) {
+					otherfile = filepath.Join(dir, otherfile)
+				}
+
+				i, err := ini.parseFile(otherfile)
+				if err != nil {
+					return nil, err
+				}
+
+				for sec, dt := range i.data {
+					if _, ok := cfg.data[sec]; !ok {
+						cfg.data[sec] = make(map[string]string)
+					}
+					for k, v := range dt {
+						cfg.data[sec][k] = v
+					}
+				}
+
+				for sec, comm := range i.sectionComment {
+					cfg.sectionComment[sec] = comm
+				}
+
+				for k, comm := range i.keyComment {
+					cfg.keyComment[k] = comm
+				}
+
+				continue
+			}
+		}
+
+		if len(keyValue) != 2 {
+			return nil, errors.New("read the content error: \"" + string(line) + "\", should key = val")
+		}
+		val := bytes.TrimSpace(keyValue[1])
+		if bytes.HasPrefix(val, bDQuote) {
+			val = bytes.Trim(val, `"`)
+		}
+
+		cfg.data[section][key] = ExpandValueEnv(string(val))
+		if comment.Len() > 0 {
+			cfg.keyComment[section+"."+key] = comment.String()
+			comment.Reset()
+		}
+
+	}
+	return cfg, nil
+}
+
+// ParseData parse ini the data
+// When include other.conf,other.conf is either absolute directory
+// or under beego in default temporary directory(/tmp/beego).
+func (ini *IniConfig) ParseData(data []byte) (Configer, error) {
+	dir := filepath.Join(os.TempDir(), "beego")
+	os.MkdirAll(dir, os.ModePerm)
+
+	return ini.parseData(dir, data)
+}
+
+// IniConfigContainer A Config represents the ini configuration.
+// When set and get value, support key as section:name type.
+type IniConfigContainer struct {
+	data           map[string]map[string]string // section=> key:val
+	sectionComment map[string]string            // section : comment
+	keyComment     map[string]string            // id: []{comment, key...}; id 1 is for main comment.
+	sync.RWMutex
+}
+
+// Bool returns the boolean value for a given key.
+func (c *IniConfigContainer) Bool(key string) (bool, error) {
+	return ParseBool(c.getdata(key))
+}
+
+// DefaultBool returns the boolean value for a given key.
+// if err != nil return defaltval
+func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
+	v, err := c.Bool(key)
+	if err != nil {
+		return defaultval
+	}
+	return v
+}
+
+// Int returns the integer value for a given key.
+func (c *IniConfigContainer) Int(key string) (int, error) {
+	return strconv.Atoi(c.getdata(key))
+}
+
+// DefaultInt returns the integer value for a given key.
+// if err != nil return defaltval
+func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
+	v, err := c.Int(key)
+	if err != nil {
+		return defaultval
+	}
+	return v
+}
+
+// Int64 returns the int64 value for a given key.
+func (c *IniConfigContainer) Int64(key string) (int64, error) {
+	return strconv.ParseInt(c.getdata(key), 10, 64)
+}
+
+// DefaultInt64 returns the int64 value for a given key.
+// if err != nil return defaltval
+func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
+	v, err := c.Int64(key)
+	if err != nil {
+		return defaultval
+	}
+	return v
+}
+
+// Float returns the float value for a given key.
+func (c *IniConfigContainer) Float(key string) (float64, error) {
+	return strconv.ParseFloat(c.getdata(key), 64)
+}
+
+// DefaultFloat returns the float64 value for a given key.
+// if err != nil return defaltval
+func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
+	v, err := c.Float(key)
+	if err != nil {
+		return defaultval
+	}
+	return v
+}
+
+// String returns the string value for a given key.
+func (c *IniConfigContainer) String(key string) string {
+	return c.getdata(key)
+}
+
+// DefaultString returns the string value for a given key.
+// if err != nil return defaltval
+func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
+	v := c.String(key)
+	if v == "" {
+		return defaultval
+	}
+	return v
+}
+
+// Strings returns the []string value for a given key.
+// Return nil if config value does not exist or is empty.
+func (c *IniConfigContainer) Strings(key string) []string {
+	v := c.String(key)
+	if v == "" {
+		return nil
+	}
+	return strings.Split(v, ";")
+}
+
+// DefaultStrings returns the []string value for a given key.
+// if err != nil return defaltval
+func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
+	v := c.Strings(key)
+	if v == nil {
+		return defaultval
+	}
+	return v
+}
+
+// GetSection returns map for the given section
+func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) {
+	if v, ok := c.data[section]; ok {
+		return v, nil
+	}
+	return nil, errors.New("not exist section")
+}
+
+// SaveConfigFile save the config into file.
+//
+// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Funcation.
+func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
+	// Write configuration file by filename.
+	f, err := os.Create(filename)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	// Get section or key comments. Fixed #1607
+	getCommentStr := func(section, key string) string {
+		comment, ok := "", false
+		if len(key) == 0 {
+			comment, ok = c.sectionComment[section]
+		} else {
+			comment, ok = c.keyComment[section+"."+key]
+		}
+
+		if ok {
+			// Empty comment
+			if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 {
+				return string(bNumComment)
+			}
+			prefix := string(bNumComment)
+			// Add the line head character "#"
+			return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1)
+		}
+		return ""
+	}
+
+	buf := bytes.NewBuffer(nil)
+	// Save default section at first place
+	if dt, ok := c.data[defaultSection]; ok {
+		for key, val := range dt {
+			if key != " " {
+				// Write key comments.
+				if v := getCommentStr(defaultSection, key); len(v) > 0 {
+					if _, err = buf.WriteString(v + lineBreak); err != nil {
+						return err
+					}
+				}
+
+				// Write key and value.
+				if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
+					return err
+				}
+			}
+		}
+
+		// Put a line between sections.
+		if _, err = buf.WriteString(lineBreak); err != nil {
+			return err
+		}
+	}
+	// Save named sections
+	for section, dt := range c.data {
+		if section != defaultSection {
+			// Write section comments.
+			if v := getCommentStr(section, ""); len(v) > 0 {
+				if _, err = buf.WriteString(v + lineBreak); err != nil {
+					return err
+				}
+			}
+
+			// Write section name.
+			if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil {
+				return err
+			}
+
+			for key, val := range dt {
+				if key != " " {
+					// Write key comments.
+					if v := getCommentStr(section, key); len(v) > 0 {
+						if _, err = buf.WriteString(v + lineBreak); err != nil {
+							return err
+						}
+					}
+
+					// Write key and value.
+					if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
+						return err
+					}
+				}
+			}
+
+			// Put a line between sections.
+			if _, err = buf.WriteString(lineBreak); err != nil {
+				return err
+			}
+		}
+	}
+
+	if _, err = buf.WriteTo(f); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Set writes a new value for key.
+// if write to one section, the key need be "section::key".
+// if the section is not existed, it panics.
+func (c *IniConfigContainer) Set(key, value string) error {
+	c.Lock()
+	defer c.Unlock()
+	if len(key) == 0 {
+		return errors.New("key is empty")
+	}
+
+	var (
+		section, k string
+		sectionKey = strings.Split(key, "::")
+	)
+
+	if len(sectionKey) >= 2 {
+		section = sectionKey[0]
+		k = sectionKey[1]
+	} else {
+		section = defaultSection
+		k = sectionKey[0]
+	}
+
+	if _, ok := c.data[section]; !ok {
+		c.data[section] = make(map[string]string)
+	}
+	c.data[section][k] = value
+	return nil
+}
+
+// DIY returns the raw value by a given key.
+func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) {
+	if v, ok := c.data[strings.ToLower(key)]; ok {
+		return v, nil
+	}
+	return v, errors.New("key not find")
+}
+
+// section.key or key
+func (c *IniConfigContainer) getdata(key string) string {
+	if len(key) == 0 {
+		return ""
+	}
+	c.RLock()
+	defer c.RUnlock()
+
+	var (
+		section, k string
+		sectionKey = strings.Split(strings.ToLower(key), "::")
+	)
+	if len(sectionKey) >= 2 {
+		section = sectionKey[0]
+		k = sectionKey[1]
+	} else {
+		section = defaultSection
+		k = sectionKey[0]
+	}
+	if v, ok := c.data[section]; ok {
+		if vv, ok := v[k]; ok {
+			return vv
+		}
+	}
+	return ""
+}
+
+func init() {
+	Register("ini", &IniConfig{})
+}

+ 190 - 0
vendor/github.com/fatedier/beego/config/ini_test.go

@@ -0,0 +1,190 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strings"
+	"testing"
+)
+
+func TestIni(t *testing.T) {
+
+	var (
+		inicontext = `
+;comment one
+#comment two
+appname = beeapi
+httpport = 8080
+mysqlport = 3600
+PI = 3.1415976
+runmode = "dev"
+autorender = false
+copyrequestbody = true
+session= on
+cookieon= off
+newreg = OFF
+needlogin = ON
+enableSession = Y
+enableCookie = N
+flag = 1
+path1 = ${GOPATH}
+path2 = ${GOPATH||/home/go}
+[demo]
+key1="asta"
+key2 = "xie"
+CaseInsensitive = true
+peers = one;two;three
+password = ${GOPATH}
+`
+
+		keyValue = map[string]interface{}{
+			"appname":               "beeapi",
+			"httpport":              8080,
+			"mysqlport":             int64(3600),
+			"pi":                    3.1415976,
+			"runmode":               "dev",
+			"autorender":            false,
+			"copyrequestbody":       true,
+			"session":               true,
+			"cookieon":              false,
+			"newreg":                false,
+			"needlogin":             true,
+			"enableSession":         true,
+			"enableCookie":          false,
+			"flag":                  true,
+			"path1":                 os.Getenv("GOPATH"),
+			"path2":                 os.Getenv("GOPATH"),
+			"demo::key1":            "asta",
+			"demo::key2":            "xie",
+			"demo::CaseInsensitive": true,
+			"demo::peers":           []string{"one", "two", "three"},
+			"demo::password":        os.Getenv("GOPATH"),
+			"null":                  "",
+			"demo2::key1":           "",
+			"error":                 "",
+			"emptystrings":          []string{},
+		}
+	)
+
+	f, err := os.Create("testini.conf")
+	if err != nil {
+		t.Fatal(err)
+	}
+	_, err = f.WriteString(inicontext)
+	if err != nil {
+		f.Close()
+		t.Fatal(err)
+	}
+	f.Close()
+	defer os.Remove("testini.conf")
+	iniconf, err := NewConfig("ini", "testini.conf")
+	if err != nil {
+		t.Fatal(err)
+	}
+	for k, v := range keyValue {
+		var err error
+		var value interface{}
+		switch v.(type) {
+		case int:
+			value, err = iniconf.Int(k)
+		case int64:
+			value, err = iniconf.Int64(k)
+		case float64:
+			value, err = iniconf.Float(k)
+		case bool:
+			value, err = iniconf.Bool(k)
+		case []string:
+			value = iniconf.Strings(k)
+		case string:
+			value = iniconf.String(k)
+		default:
+			value, err = iniconf.DIY(k)
+		}
+		if err != nil {
+			t.Fatalf("get key %q value fail,err %s", k, err)
+		} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
+			t.Fatalf("get key %q value, want %v got %v .", k, v, value)
+		}
+
+	}
+	if err = iniconf.Set("name", "astaxie"); err != nil {
+		t.Fatal(err)
+	}
+	if iniconf.String("name") != "astaxie" {
+		t.Fatal("get name error")
+	}
+
+}
+
+func TestIniSave(t *testing.T) {
+
+	const (
+		inicontext = `
+app = app
+;comment one
+#comment two
+# comment three
+appname = beeapi
+httpport = 8080
+# DB Info
+# enable db
+[dbinfo]
+# db type name
+# suport mysql,sqlserver
+name = mysql
+`
+
+		saveResult = `
+app=app
+#comment one
+#comment two
+# comment three
+appname=beeapi
+httpport=8080
+
+# DB Info
+# enable db
+[dbinfo]
+# db type name
+# suport mysql,sqlserver
+name=mysql
+`
+	)
+	cfg, err := NewConfigData("ini", []byte(inicontext))
+	if err != nil {
+		t.Fatal(err)
+	}
+	name := "newIniConfig.ini"
+	if err := cfg.SaveConfigFile(name); err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(name)
+
+	if data, err := ioutil.ReadFile(name); err != nil {
+		t.Fatal(err)
+	} else {
+		cfgData := string(data)
+		datas := strings.Split(saveResult, "\n")
+		for _, line := range datas {
+			if strings.Contains(cfgData, line+"\n") == false {
+				t.Fatalf("different after save ini config file. need contains %q", line)
+			}
+		}
+
+	}
+}

+ 266 - 0
vendor/github.com/fatedier/beego/config/json.go

@@ -0,0 +1,266 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strings"
+	"sync"
+)
+
+// JSONConfig is a json config parser and implements Config interface.
+type JSONConfig struct {
+}
+
+// Parse returns a ConfigContainer with parsed json config map.
+func (js *JSONConfig) Parse(filename string) (Configer, error) {
+	file, err := os.Open(filename)
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+	content, err := ioutil.ReadAll(file)
+	if err != nil {
+		return nil, err
+	}
+
+	return js.ParseData(content)
+}
+
+// ParseData returns a ConfigContainer with json string
+func (js *JSONConfig) ParseData(data []byte) (Configer, error) {
+	x := &JSONConfigContainer{
+		data: make(map[string]interface{}),
+	}
+	err := json.Unmarshal(data, &x.data)
+	if err != nil {
+		var wrappingArray []interface{}
+		err2 := json.Unmarshal(data, &wrappingArray)
+		if err2 != nil {
+			return nil, err
+		}
+		x.data["rootArray"] = wrappingArray
+	}
+
+	x.data = ExpandValueEnvForMap(x.data)
+
+	return x, nil
+}
+
+// JSONConfigContainer A Config represents the json configuration.
+// Only when get value, support key as section:name type.
+type JSONConfigContainer struct {
+	data map[string]interface{}
+	sync.RWMutex
+}
+
+// Bool returns the boolean value for a given key.
+func (c *JSONConfigContainer) Bool(key string) (bool, error) {
+	val := c.getData(key)
+	if val != nil {
+		return ParseBool(val)
+	}
+	return false, fmt.Errorf("not exist key: %q", key)
+}
+
+// DefaultBool return the bool value if has no error
+// otherwise return the defaultval
+func (c *JSONConfigContainer) DefaultBool(key string, defaultval bool) bool {
+	if v, err := c.Bool(key); err == nil {
+		return v
+	}
+	return defaultval
+}
+
+// Int returns the integer value for a given key.
+func (c *JSONConfigContainer) Int(key string) (int, error) {
+	val := c.getData(key)
+	if val != nil {
+		if v, ok := val.(float64); ok {
+			return int(v), nil
+		}
+		return 0, errors.New("not int value")
+	}
+	return 0, errors.New("not exist key:" + key)
+}
+
+// DefaultInt returns the integer value for a given key.
+// if err != nil return defaltval
+func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int {
+	if v, err := c.Int(key); err == nil {
+		return v
+	}
+	return defaultval
+}
+
+// Int64 returns the int64 value for a given key.
+func (c *JSONConfigContainer) Int64(key string) (int64, error) {
+	val := c.getData(key)
+	if val != nil {
+		if v, ok := val.(float64); ok {
+			return int64(v), nil
+		}
+		return 0, errors.New("not int64 value")
+	}
+	return 0, errors.New("not exist key:" + key)
+}
+
+// DefaultInt64 returns the int64 value for a given key.
+// if err != nil return defaltval
+func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
+	if v, err := c.Int64(key); err == nil {
+		return v
+	}
+	return defaultval
+}
+
+// Float returns the float value for a given key.
+func (c *JSONConfigContainer) Float(key string) (float64, error) {
+	val := c.getData(key)
+	if val != nil {
+		if v, ok := val.(float64); ok {
+			return v, nil
+		}
+		return 0.0, errors.New("not float64 value")
+	}
+	return 0.0, errors.New("not exist key:" + key)
+}
+
+// DefaultFloat returns the float64 value for a given key.
+// if err != nil return defaltval
+func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
+	if v, err := c.Float(key); err == nil {
+		return v
+	}
+	return defaultval
+}
+
+// String returns the string value for a given key.
+func (c *JSONConfigContainer) String(key string) string {
+	val := c.getData(key)
+	if val != nil {
+		if v, ok := val.(string); ok {
+			return v
+		}
+	}
+	return ""
+}
+
+// DefaultString returns the string value for a given key.
+// if err != nil return defaltval
+func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string {
+	// TODO FIXME should not use "" to replace non existence
+	if v := c.String(key); v != "" {
+		return v
+	}
+	return defaultval
+}
+
+// Strings returns the []string value for a given key.
+func (c *JSONConfigContainer) Strings(key string) []string {
+	stringVal := c.String(key)
+	if stringVal == "" {
+		return nil
+	}
+	return strings.Split(c.String(key), ";")
+}
+
+// DefaultStrings returns the []string value for a given key.
+// if err != nil return defaltval
+func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string {
+	if v := c.Strings(key); v != nil {
+		return v
+	}
+	return defaultval
+}
+
+// GetSection returns map for the given section
+func (c *JSONConfigContainer) GetSection(section string) (map[string]string, error) {
+	if v, ok := c.data[section]; ok {
+		return v.(map[string]string), nil
+	}
+	return nil, errors.New("nonexist section " + section)
+}
+
+// SaveConfigFile save the config into file
+func (c *JSONConfigContainer) SaveConfigFile(filename string) (err error) {
+	// Write configuration file by filename.
+	f, err := os.Create(filename)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	b, err := json.MarshalIndent(c.data, "", "  ")
+	if err != nil {
+		return err
+	}
+	_, err = f.Write(b)
+	return err
+}
+
+// Set writes a new value for key.
+func (c *JSONConfigContainer) Set(key, val string) error {
+	c.Lock()
+	defer c.Unlock()
+	c.data[key] = val
+	return nil
+}
+
+// DIY returns the raw value by a given key.
+func (c *JSONConfigContainer) DIY(key string) (v interface{}, err error) {
+	val := c.getData(key)
+	if val != nil {
+		return val, nil
+	}
+	return nil, errors.New("not exist key")
+}
+
+// section.key or key
+func (c *JSONConfigContainer) getData(key string) interface{} {
+	if len(key) == 0 {
+		return nil
+	}
+
+	c.RLock()
+	defer c.RUnlock()
+
+	sectionKeys := strings.Split(key, "::")
+	if len(sectionKeys) >= 2 {
+		curValue, ok := c.data[sectionKeys[0]]
+		if !ok {
+			return nil
+		}
+		for _, key := range sectionKeys[1:] {
+			if v, ok := curValue.(map[string]interface{}); ok {
+				if curValue, ok = v[key]; !ok {
+					return nil
+				}
+			}
+		}
+		return curValue
+	}
+	if v, ok := c.data[key]; ok {
+		return v
+	}
+	return nil
+}
+
+func init() {
+	Register("json", &JSONConfig{})
+}

Some files were not shown because too many files changed in this diff