newtmgr: dep ensure -update github.com/go-ble/ble
authorŁukasz Rymanowski <lukasz.rymanowski@codecoup.pl>
Wed, 3 Oct 2018 08:06:59 +0000 (10:06 +0200)
committerŁukasz Rymanowski <lukasz.rymanowski@codecoup.pl>
Wed, 3 Oct 2018 08:08:53 +0000 (10:08 +0200)
227 files changed:
Gopkg.lock
vendor/github.com/Sirupsen/logrus/.travis.yml
vendor/github.com/Sirupsen/logrus/CHANGELOG.md
vendor/github.com/Sirupsen/logrus/README.md
vendor/github.com/Sirupsen/logrus/entry.go
vendor/github.com/Sirupsen/logrus/entry_test.go
vendor/github.com/Sirupsen/logrus/example_basic_test.go
vendor/github.com/Sirupsen/logrus/example_hook_test.go
vendor/github.com/Sirupsen/logrus/exported.go
vendor/github.com/Sirupsen/logrus/formatter.go
vendor/github.com/Sirupsen/logrus/go.mod [new file with mode: 0644]
vendor/github.com/Sirupsen/logrus/go.sum [new file with mode: 0644]
vendor/github.com/Sirupsen/logrus/hook_test.go
vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go
vendor/github.com/Sirupsen/logrus/hooks/test/test.go
vendor/github.com/Sirupsen/logrus/hooks/test/test_test.go
vendor/github.com/Sirupsen/logrus/json_formatter.go
vendor/github.com/Sirupsen/logrus/json_formatter_test.go
vendor/github.com/Sirupsen/logrus/logger.go
vendor/github.com/Sirupsen/logrus/logger_bench_test.go
vendor/github.com/Sirupsen/logrus/logrus.go
vendor/github.com/Sirupsen/logrus/logrus_test.go
vendor/github.com/Sirupsen/logrus/terminal_appengine.go [new file with mode: 0644]
vendor/github.com/Sirupsen/logrus/terminal_bsd.go
vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go [new file with mode: 0644]
vendor/github.com/Sirupsen/logrus/terminal_check_js.go [new file with mode: 0644]
vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go [new file with mode: 0644]
vendor/github.com/Sirupsen/logrus/terminal_check_windows.go [new file with mode: 0644]
vendor/github.com/Sirupsen/logrus/terminal_linux.go
vendor/github.com/Sirupsen/logrus/terminal_windows.go [new file with mode: 0644]
vendor/github.com/Sirupsen/logrus/text_formatter.go
vendor/github.com/Sirupsen/logrus/text_formatter_test.go
vendor/github.com/go-ble/ble/.travis.yml
vendor/github.com/go-ble/ble/client.go
vendor/github.com/go-ble/ble/darwin/client.go
vendor/github.com/go-ble/ble/darwin/conn.go
vendor/github.com/go-ble/ble/darwin/device.go
vendor/github.com/go-ble/ble/darwin/msg.go
vendor/github.com/go-ble/ble/darwin/option.go
vendor/github.com/go-ble/ble/examples/blesh/lnx.go
vendor/github.com/go-ble/ble/examples/lib/dev/default_darwin.go
vendor/github.com/go-ble/ble/examples/lib/dev/default_linux.go
vendor/github.com/go-ble/ble/examples/lib/dev/dev.go
vendor/github.com/go-ble/ble/linux/adv/packet.go
vendor/github.com/go-ble/ble/linux/att/db.go
vendor/github.com/go-ble/ble/linux/att/server.go
vendor/github.com/go-ble/ble/linux/device.go
vendor/github.com/go-ble/ble/linux/gatt/client.go
vendor/github.com/go-ble/ble/linux/hci/conn.go
vendor/github.com/go-ble/ble/linux/hci/gap.go
vendor/github.com/go-ble/ble/linux/hci/hci.go
vendor/github.com/go-ble/ble/linux/hci/option.go
vendor/github.com/go-ble/ble/linux/hci/signal.go
vendor/github.com/go-ble/ble/linux/hci/signal_gen.go
vendor/github.com/go-ble/ble/linux/hci/smp.go
vendor/github.com/go-ble/ble/option.go [new file with mode: 0644]
vendor/github.com/go-ble/ble/uuid.go
vendor/github.com/go-ble/ble/uuid_test.go [new file with mode: 0644]
vendor/github.com/konsorten/go-windows-terminal-sequences/README.md [new file with mode: 0644]
vendor/github.com/konsorten/go-windows-terminal-sequences/license [new file with mode: 0644]
vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go [new file with mode: 0644]
vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_test.go [new file with mode: 0644]
vendor/github.com/kr/pretty/go.mod [new file with mode: 0644]
vendor/github.com/spf13/cast/cast_test.go
vendor/github.com/spf13/cast/caste.go
vendor/github.com/spf13/pflag/bytes.go [new file with mode: 0644]
vendor/github.com/spf13/pflag/bytes_test.go [new file with mode: 0644]
vendor/github.com/spf13/pflag/count.go
vendor/github.com/spf13/pflag/count_test.go
vendor/github.com/spf13/pflag/duration_slice.go [new file with mode: 0644]
vendor/github.com/spf13/pflag/duration_slice_test.go [new file with mode: 0644]
vendor/github.com/spf13/pflag/flag.go
vendor/github.com/spf13/pflag/flag_test.go
vendor/github.com/spf13/pflag/golangflag.go
vendor/github.com/spf13/pflag/golangflag_test.go
vendor/github.com/spf13/pflag/int16.go [new file with mode: 0644]
vendor/github.com/spf13/pflag/printusage_test.go [new file with mode: 0644]
vendor/github.com/spf13/pflag/string_array.go
vendor/github.com/spf13/pflag/string_slice.go
vendor/github.com/spf13/pflag/string_to_int.go [new file with mode: 0644]
vendor/github.com/spf13/pflag/string_to_int_test.go [new file with mode: 0644]
vendor/github.com/spf13/pflag/string_to_string.go [new file with mode: 0644]
vendor/github.com/spf13/pflag/string_to_string_test.go [new file with mode: 0644]
vendor/mynewt.apache.org/newt/.rat-excludes
vendor/mynewt.apache.org/newt/Gopkg.lock [new file with mode: 0644]
vendor/mynewt.apache.org/newt/Gopkg.toml [new file with mode: 0644]
vendor/mynewt.apache.org/newt/LICENSE
vendor/mynewt.apache.org/newt/Makefile [new file with mode: 0644]
vendor/mynewt.apache.org/newt/README.md
vendor/mynewt.apache.org/newt/RELEASE_NOTES.md
vendor/mynewt.apache.org/newt/build.sh
vendor/mynewt.apache.org/newt/docs/.gitignore [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/Makefile [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/README.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_build.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_clean.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_complete.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_create_image.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_debug.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_help.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_info.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_install.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_load.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_mfg.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_new.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_pkg.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_resign_image.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_run.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_size.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_sync.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_target.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_test.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_upgrade.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_vals.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/command_list/newt_version.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/conf.py [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/index.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/install/index.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/install/newt_linux.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/install/newt_mac.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/install/newt_windows.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/install/prev_releases.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/newt_operation.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/docs/newt_ops.rst [new file with mode: 0644]
vendor/mynewt.apache.org/newt/newt/Godeps/Godeps.json [deleted file]
vendor/mynewt.apache.org/newt/newt/Godeps/Readme [deleted file]
vendor/mynewt.apache.org/newt/newt/builder/build.go
vendor/mynewt.apache.org/newt/newt/builder/buildpackage.go
vendor/mynewt.apache.org/newt/newt/builder/buildutil.go
vendor/mynewt.apache.org/newt/newt/builder/cmake.go [new file with mode: 0644]
vendor/mynewt.apache.org/newt/newt/builder/depgraph.go
vendor/mynewt.apache.org/newt/newt/builder/library.go
vendor/mynewt.apache.org/newt/newt/builder/load.go
vendor/mynewt.apache.org/newt/newt/builder/size.go
vendor/mynewt.apache.org/newt/newt/builder/size_report.go
vendor/mynewt.apache.org/newt/newt/builder/targetbuild.go
vendor/mynewt.apache.org/newt/newt/cli/build_cmds.go
vendor/mynewt.apache.org/newt/newt/cli/image_cmds.go
vendor/mynewt.apache.org/newt/newt/cli/project_cmds.go
vendor/mynewt.apache.org/newt/newt/cli/run_cmds.go
vendor/mynewt.apache.org/newt/newt/cli/target_cmds.go
vendor/mynewt.apache.org/newt/newt/cli/vars.go
vendor/mynewt.apache.org/newt/newt/compat/compat.go
vendor/mynewt.apache.org/newt/newt/deprepo/deprepo.go [new file with mode: 0644]
vendor/mynewt.apache.org/newt/newt/deprepo/graph.go [new file with mode: 0644]
vendor/mynewt.apache.org/newt/newt/deprepo/matrix.go [new file with mode: 0644]
vendor/mynewt.apache.org/newt/newt/downloader/downloader.go
vendor/mynewt.apache.org/newt/newt/image/encrypted.go [new file with mode: 0644]
vendor/mynewt.apache.org/newt/newt/image/image.go
vendor/mynewt.apache.org/newt/newt/image/keys_test.go [new file with mode: 0644]
vendor/mynewt.apache.org/newt/newt/install/install.go [new file with mode: 0644]
vendor/mynewt.apache.org/newt/newt/interfaces/interfaces.go
vendor/mynewt.apache.org/newt/newt/mfg/load.go
vendor/mynewt.apache.org/newt/newt/newtutil/newtutil.go
vendor/mynewt.apache.org/newt/newt/newtutil/repo_version.go [new file with mode: 0644]
vendor/mynewt.apache.org/newt/newt/parse/lex.go [new file with mode: 0644]
vendor/mynewt.apache.org/newt/newt/parse/parse.go [new file with mode: 0644]
vendor/mynewt.apache.org/newt/newt/pkg/bsp_package.go
vendor/mynewt.apache.org/newt/newt/pkg/localpackage.go
vendor/mynewt.apache.org/newt/newt/pkg/package.go
vendor/mynewt.apache.org/newt/newt/project/pkgwriter.go
vendor/mynewt.apache.org/newt/newt/project/project.go
vendor/mynewt.apache.org/newt/newt/project/projectstate.go [deleted file]
vendor/mynewt.apache.org/newt/newt/repo/repo.go
vendor/mynewt.apache.org/newt/newt/repo/version.go
vendor/mynewt.apache.org/newt/newt/resolve/resolve.go
vendor/mynewt.apache.org/newt/newt/settings/settings.go [moved from vendor/mynewt.apache.org/newt/newtmgr/cli/usage.go with 56% similarity]
vendor/mynewt.apache.org/newt/newt/syscfg/restrict.go
vendor/mynewt.apache.org/newt/newt/syscfg/syscfg.go
vendor/mynewt.apache.org/newt/newt/target/target.go
vendor/mynewt.apache.org/newt/newt/toolchain/compiler.go
vendor/mynewt.apache.org/newt/newt/ycfg/ycfg.go [new file with mode: 0644]
vendor/mynewt.apache.org/newt/newtmgr/Godeps/Godeps.json [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/Godeps/Readme [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/commands.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/common.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/config.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/connprofile.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/crash.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/datetime.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/echo.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/echoctrl.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/fs.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/image.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/logs.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/mpstats.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/reset.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/runtest.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/stats.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/cli/taskstats.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/config/connprofile.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/core/core_convert.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/newtmgr.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/nmutil/nmutil.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/cmdrunner.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/config.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/coreerase.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/corelist.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/coreload.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/crash.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/datetime.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/defs.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/echo.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/fsdefs.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/fsdownload.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/fsupload.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/imagedefs.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/imageerase.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/imagestate.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/imageupload.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/logs.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/mpstats.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/nmgr.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/omgr.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/reset.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/runtest.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/stats.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/protocol/taskstats.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/transport/conn.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/transport/connble.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/transport/connble_darwin.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/transport/connble_linux.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/transport/connserial.go [deleted file]
vendor/mynewt.apache.org/newt/newtmgr/transport/connudp.go [deleted file]
vendor/mynewt.apache.org/newt/util/unixchild/unixchild.go
vendor/mynewt.apache.org/newt/util/util.go
vendor/mynewt.apache.org/newt/yaml/misc.go

index 12d0570..a1b5ae2 100644 (file)
@@ -4,8 +4,8 @@
 [[projects]]
   name = "github.com/Sirupsen/logrus"
   packages = ["."]
-  revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e"
-  version = "v1.0.3"
+  revision = "a67f783a3814b8729bd2dac5780b5f78f8dbd64d"
+  version = "v1.1.0"
 
 [[projects]]
   name = "github.com/cheggaaa/pb"
 
 [[projects]]
   branch = "master"
+  name = "github.com/konsorten/go-windows-terminal-sequences"
+  packages = ["."]
+  revision = "b729f2633dfe35f4d1d8a32385f6685610ce1cb5"
+
+[[projects]]
   name = "github.com/kr/pretty"
   packages = ["."]
-  revision = "cfb55aafdaf3ec08f0db22699ab822c50091b1c4"
+  revision = "73f6ac0b30a98e433b289500d779f50c1a6f0712"
+  version = "v0.1.0"
 
 [[projects]]
   branch = "master"
 [[projects]]
   name = "github.com/spf13/cast"
   packages = ["."]
-  revision = "acbeb36b902d72a7a4c18e8f3241075e7ab763e4"
-  version = "v1.1.0"
+  revision = "8965335b8c7107321228e3e3702cab9832751bac"
+  version = "v1.2.0"
 
 [[projects]]
   branch = "master"
 [[projects]]
   name = "github.com/spf13/pflag"
   packages = ["."]
-  revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
-  version = "v1.0.0"
+  revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
+  version = "v1.0.3"
 
 [[projects]]
   branch = "master"
   name = "gopkg.in/fsnotify.v1"
   packages = ["."]
   revision = "629574ca2a5df945712d3079857300b5e4da0236"
+  source = "https://github.com/fsnotify/fsnotify.git"
   version = "v1.4.2"
 
 [[projects]]
   branch = "master"
   name = "mynewt.apache.org/newt"
   packages = ["util","util/unixchild","viper","yaml"]
-  revision = "773413552ff0e4e7d5a04c526da089a33c21680f"
+  revision = "757fadf0338039637436ee990f55ec12d48e9439"
 
 [solve-meta]
   analyzer-name = "dep"
   analyzer-version = 1
-  inputs-digest = "e2cc5a4fab95a5eccf8d614680b609c21280660ed87513eacbda6f9d0072b9c8"
+  inputs-digest = "214d5c29d58ea2f019ab38724d7b4211b816f30df75d00960a2ffecab6abe2f9"
   solver-name = "gps-cdcl"
   solver-version = 1
index a23296a..1f953be 100644 (file)
@@ -1,15 +1,51 @@
 language: go
-go:
-  - 1.6.x
-  - 1.7.x
-  - 1.8.x
-  - tip
 env:
   - GOMAXPROCS=4 GORACE=halt_on_error=1
-install:
-  - go get github.com/stretchr/testify/assert
-  - go get gopkg.in/gemnasium/logrus-airbrake-hook.v2
-  - go get golang.org/x/sys/unix
-  - go get golang.org/x/sys/windows
-script:
-  - go test -race -v ./...
+matrix:
+  include:
+    - go: 1.10.x
+      install:
+        - go get github.com/stretchr/testify/assert
+        - go get golang.org/x/crypto/ssh/terminal
+        - go get golang.org/x/sys/unix
+        - go get golang.org/x/sys/windows
+      script:
+        - go test -race -v ./...
+    - go: 1.11.x
+      env: GO111MODULE=on
+      install:
+        - go mod download
+      script:
+        - go test -race -v ./...
+    - go: 1.11.x
+      env: GO111MODULE=off
+      install:
+        - go get github.com/stretchr/testify/assert
+        - go get golang.org/x/crypto/ssh/terminal
+        - go get golang.org/x/sys/unix
+        - go get golang.org/x/sys/windows
+      script:
+        - go test -race -v ./...
+    - go: 1.10.x
+      install:
+        - go get github.com/stretchr/testify/assert
+        - go get golang.org/x/crypto/ssh/terminal
+        - go get golang.org/x/sys/unix
+        - go get golang.org/x/sys/windows
+      script:
+        - go test -race -v -tags appengine ./...
+    - go: 1.11.x
+      env: GO111MODULE=on
+      install:
+        - go mod download
+      script:
+        - go test -race -v -tags appengine ./...
+    - go: 1.11.x
+      env: GO111MODULE=off
+      install:
+        - go get github.com/stretchr/testify/assert
+        - go get golang.org/x/crypto/ssh/terminal
+        - go get golang.org/x/sys/unix
+        - go get golang.org/x/sys/windows
+      script:
+        - go test -race -v -tags appengine ./...
index 8236d8b..1702696 100644 (file)
@@ -1,3 +1,43 @@
+# 1.1.0
+This new release introduces:
+  * several fixes:
+    * a fix for a race condition on entry formatting
+    * proper cleanup of previously used entries before putting them back in the pool
+    * the extra new line at the end of message in text formatter has been removed
+  * a new global public API to check if a level is activated: IsLevelEnabled
+  * the following methods have been added to the Logger object
+    * IsLevelEnabled
+    * SetFormatter
+    * SetOutput
+    * ReplaceHooks
+  * introduction of go module
+  * an indent configuration for the json formatter
+  * output colour support for windows
+  * the field sort function is now configurable for text formatter
+  * the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater
+
+# 1.0.6
+
+This new release introduces:
+  * a new api WithTime which allows to easily force the time of the log entry
+    which is mostly useful for logger wrapper
+  * a fix reverting the immutability of the entry given as parameter to the hooks
+    a new configuration field of the json formatter in order to put all the fields
+    in a nested dictionnary
+  * a new SetOutput method in the Logger
+  * a new configuration of the textformatter to configure the name of the default keys
+  * a new configuration of the text formatter to disable the level truncation
+
+# 1.0.5
+
+* Fix hooks race (#707)
+* Fix panic deadlock (#695)
+
+# 1.0.4
+
+* Fix race when adding hooks (#612)
+* Fix terminal check in AppEngine (#635)
+
 # 1.0.3
 
 * Replace example files with testable examples
index 4f5ce57..072e99b 100644 (file)
@@ -220,7 +220,7 @@ Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
 ```go
 import (
   log "github.com/sirupsen/logrus"
-  "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
+  "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
   logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
   "log/syslog"
 )
@@ -241,54 +241,8 @@ func init() {
 ```
 Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
 
-| Hook  | Description |
-| ----- | ----------- |
-| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
-| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. |
-| [Amazon Kinesis](https://github.com/evalphobia/logrus_kinesis) | Hook for logging to [Amazon Kinesis](https://aws.amazon.com/kinesis/) |
-| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) |
-| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
-| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
-| [Discordrus](https://github.com/kz/discordrus) | Hook for logging to [Discord](https://discordapp.com/) |
-| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch|
-| [Firehose](https://github.com/beaubrewer/logrus_firehose) | Hook for logging to [Amazon Firehose](https://aws.amazon.com/kinesis/firehose/)
-| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
-| [Go-Slack](https://github.com/multiplay/go-slack) | Hook for logging to [Slack](https://slack.com) |
-| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
-| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
-| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
-| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
-| [Influxus](http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB](http://influxdata.com/) |
-| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
-| [KafkaLogrus](https://github.com/goibibo/KafkaLogrus) | Hook for logging to kafka |
-| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
-| [Logentries](https://github.com/jcftang/logentriesrus) | Hook for logging to [Logentries](https://logentries.com/) |
-| [Logentrus](https://github.com/puddingfactory/logentrus) | Hook for logging to [Logentries](https://logentries.com/) |
-| [Logmatic.io](https://github.com/logmatic/logmatic-go) | Hook for logging to [Logmatic.io](http://logmatic.io/) |
-| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
-| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
-| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
-| [Mattermost](https://github.com/shuLhan/mattermost-integration/tree/master/hooks/logrus) | Hook for logging to [Mattermost](https://mattermost.com/) |
-| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
-| [NATS-Hook](https://github.com/rybit/nats_logrus_hook) | Hook for logging to [NATS](https://nats.io) |
-| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
-| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
-| [PostgreSQL](https://github.com/gemnasium/logrus-postgresql-hook) | Send logs to [PostgreSQL](http://postgresql.org) |
-| [Pushover](https://github.com/toorop/logrus_pushover) | Send error via [Pushover](https://pushover.net) |
-| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) |
-| [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) |
-| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
-| [Scribe](https://github.com/sagar8192/logrus-scribe-hook) | Hook for logging to [Scribe](https://github.com/facebookarchive/scribe)|
-| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
-| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
-| [Stackdriver](https://github.com/knq/sdhook) | Hook for logging to [Google Stackdriver](https://cloud.google.com/logging/) |
-| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
-| [Syslog](https://github.com/sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
-| [Syslog TLS](https://github.com/shinji62/logrus-syslog-ng) | Send errors to remote syslog server with TLS support. |
-| [TraceView](https://github.com/evalphobia/logrus_appneta) | Hook for logging to [AppNeta TraceView](https://www.appneta.com/products/traceview/) |
-| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
-| [logz.io](https://github.com/ripcurld00d/logrus-logzio-hook) | Hook for logging to [logz.io](https://logz.io), a Log as a Service using Logstash |
-| [SQS-Hook](https://github.com/tsarpaul/logrus_sqs) | Hook for logging to [Amazon Simple Queue Service (SQS)](https://aws.amazon.com/sqs/) |
+A list of currently known of service hook can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
+
 
 #### Level logging
 
@@ -366,13 +320,15 @@ The built-in logging formatters are:
     field to `true`.  To force no colored output even if there is a TTY  set the
     `DisableColors` field to `true`. For Windows, see
     [github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
+  * When colors are enabled, levels are truncated to 4 characters by default. To disable
+    truncation set the `DisableLevelTruncation` field to `true`.
   * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
 * `logrus.JSONFormatter`. Logs fields as JSON.
   * All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
 
 Third party logging formatters:
 
-* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can by parsed by Kubernetes and Google Container Engine.
+* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine.
 * [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
 * [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
 * [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
@@ -489,7 +445,7 @@ logrus.RegisterExitHandler(handler)
 
 #### Thread safety
 
-By default Logger is protected by mutex for concurrent writes, this mutex is invoked when calling hooks and writing logs.
+By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs.
 If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
 
 Situation when locking is not needed includes:
index 5bf582e..4efeddd 100644 (file)
@@ -41,14 +41,14 @@ type Entry struct {
        // Message passed to Debug, Info, Warn, Error, Fatal or Panic
        Message string
 
-       // When formatter is called in entry.log(), an Buffer may be set to entry
+       // When formatter is called in entry.log(), a Buffer may be set to entry
        Buffer *bytes.Buffer
 }
 
 func NewEntry(logger *Logger) *Entry {
        return &Entry{
                Logger: logger,
-               // Default is three fields, give a little extra room
+               // Default is five fields, give a little extra room
                Data: make(Fields, 5),
        }
 }
@@ -83,51 +83,75 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
        for k, v := range fields {
                data[k] = v
        }
-       return &Entry{Logger: entry.Logger, Data: data}
+       return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time}
+}
+
+// Overrides the time of the Entry.
+func (entry *Entry) WithTime(t time.Time) *Entry {
+       return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t}
 }
 
 // This function is not declared with a pointer value because otherwise
 // race conditions will occur when using multiple goroutines
 func (entry Entry) log(level Level, msg string) {
        var buffer *bytes.Buffer
-       entry.Time = time.Now()
+
+       // Default to now, but allow users to override if they want.
+       //
+       // We don't have to worry about polluting future calls to Entry#log()
+       // with this assignment because this function is declared with a
+       // non-pointer receiver.
+       if entry.Time.IsZero() {
+               entry.Time = time.Now()
+       }
+
        entry.Level = level
        entry.Message = msg
 
-       if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
-               entry.Logger.mu.Lock()
-               fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
-               entry.Logger.mu.Unlock()
-       }
+       entry.fireHooks()
+
        buffer = bufferPool.Get().(*bytes.Buffer)
        buffer.Reset()
        defer bufferPool.Put(buffer)
        entry.Buffer = buffer
-       serialized, err := entry.Logger.Formatter.Format(&entry)
+
+       entry.write()
+
        entry.Buffer = nil
+
+       // To avoid Entry#log() returning a value that only would make sense for
+       // panic() to use in Entry#Panic(), we avoid the allocation by checking
+       // directly here.
+       if level <= PanicLevel {
+               panic(&entry)
+       }
+}
+
+func (entry *Entry) fireHooks() {
+       entry.Logger.mu.Lock()
+       defer entry.Logger.mu.Unlock()
+       err := entry.Logger.Hooks.Fire(entry.Level, entry)
+       if err != nil {
+               fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
+       }
+}
+
+func (entry *Entry) write() {
+       entry.Logger.mu.Lock()
+       defer entry.Logger.mu.Unlock()
+       serialized, err := entry.Logger.Formatter.Format(entry)
        if err != nil {
-               entry.Logger.mu.Lock()
                fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
-               entry.Logger.mu.Unlock()
        } else {
-               entry.Logger.mu.Lock()
                _, err = entry.Logger.Out.Write(serialized)
                if err != nil {
                        fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
                }
-               entry.Logger.mu.Unlock()
-       }
-
-       // To avoid Entry#log() returning a value that only would make sense for
-       // panic() to use in Entry#Panic(), we avoid the allocation by checking
-       // directly here.
-       if level <= PanicLevel {
-               panic(&entry)
        }
 }
 
 func (entry *Entry) Debug(args ...interface{}) {
-       if entry.Logger.level() >= DebugLevel {
+       if entry.Logger.IsLevelEnabled(DebugLevel) {
                entry.log(DebugLevel, fmt.Sprint(args...))
        }
 }
@@ -137,13 +161,13 @@ func (entry *Entry) Print(args ...interface{}) {
 }
 
 func (entry *Entry) Info(args ...interface{}) {
-       if entry.Logger.level() >= InfoLevel {
+       if entry.Logger.IsLevelEnabled(InfoLevel) {
                entry.log(InfoLevel, fmt.Sprint(args...))
        }
 }
 
 func (entry *Entry) Warn(args ...interface{}) {
-       if entry.Logger.level() >= WarnLevel {
+       if entry.Logger.IsLevelEnabled(WarnLevel) {
                entry.log(WarnLevel, fmt.Sprint(args...))
        }
 }
@@ -153,20 +177,20 @@ func (entry *Entry) Warning(args ...interface{}) {
 }
 
 func (entry *Entry) Error(args ...interface{}) {
-       if entry.Logger.level() >= ErrorLevel {
+       if entry.Logger.IsLevelEnabled(ErrorLevel) {
                entry.log(ErrorLevel, fmt.Sprint(args...))
        }
 }
 
 func (entry *Entry) Fatal(args ...interface{}) {
-       if entry.Logger.level() >= FatalLevel {
+       if entry.Logger.IsLevelEnabled(FatalLevel) {
                entry.log(FatalLevel, fmt.Sprint(args...))
        }
        Exit(1)
 }
 
 func (entry *Entry) Panic(args ...interface{}) {
-       if entry.Logger.level() >= PanicLevel {
+       if entry.Logger.IsLevelEnabled(PanicLevel) {
                entry.log(PanicLevel, fmt.Sprint(args...))
        }
        panic(fmt.Sprint(args...))
@@ -175,13 +199,13 @@ func (entry *Entry) Panic(args ...interface{}) {
 // Entry Printf family functions
 
 func (entry *Entry) Debugf(format string, args ...interface{}) {
-       if entry.Logger.level() >= DebugLevel {
+       if entry.Logger.IsLevelEnabled(DebugLevel) {
                entry.Debug(fmt.Sprintf(format, args...))
        }
 }
 
 func (entry *Entry) Infof(format string, args ...interface{}) {
-       if entry.Logger.level() >= InfoLevel {
+       if entry.Logger.IsLevelEnabled(InfoLevel) {
                entry.Info(fmt.Sprintf(format, args...))
        }
 }
@@ -191,7 +215,7 @@ func (entry *Entry) Printf(format string, args ...interface{}) {
 }
 
 func (entry *Entry) Warnf(format string, args ...interface{}) {
-       if entry.Logger.level() >= WarnLevel {
+       if entry.Logger.IsLevelEnabled(WarnLevel) {
                entry.Warn(fmt.Sprintf(format, args...))
        }
 }
@@ -201,20 +225,20 @@ func (entry *Entry) Warningf(format string, args ...interface{}) {
 }
 
 func (entry *Entry) Errorf(format string, args ...interface{}) {
-       if entry.Logger.level() >= ErrorLevel {
+       if entry.Logger.IsLevelEnabled(ErrorLevel) {
                entry.Error(fmt.Sprintf(format, args...))
        }
 }
 
 func (entry *Entry) Fatalf(format string, args ...interface{}) {
-       if entry.Logger.level() >= FatalLevel {
+       if entry.Logger.IsLevelEnabled(FatalLevel) {
                entry.Fatal(fmt.Sprintf(format, args...))
        }
        Exit(1)
 }
 
 func (entry *Entry) Panicf(format string, args ...interface{}) {
-       if entry.Logger.level() >= PanicLevel {
+       if entry.Logger.IsLevelEnabled(PanicLevel) {
                entry.Panic(fmt.Sprintf(format, args...))
        }
 }
@@ -222,13 +246,13 @@ func (entry *Entry) Panicf(format string, args ...interface{}) {
 // Entry Println family functions
 
 func (entry *Entry) Debugln(args ...interface{}) {
-       if entry.Logger.level() >= DebugLevel {
+       if entry.Logger.IsLevelEnabled(DebugLevel) {
                entry.Debug(entry.sprintlnn(args...))
        }
 }
 
 func (entry *Entry) Infoln(args ...interface{}) {
-       if entry.Logger.level() >= InfoLevel {
+       if entry.Logger.IsLevelEnabled(InfoLevel) {
                entry.Info(entry.sprintlnn(args...))
        }
 }
@@ -238,7 +262,7 @@ func (entry *Entry) Println(args ...interface{}) {
 }
 
 func (entry *Entry) Warnln(args ...interface{}) {
-       if entry.Logger.level() >= WarnLevel {
+       if entry.Logger.IsLevelEnabled(WarnLevel) {
                entry.Warn(entry.sprintlnn(args...))
        }
 }
@@ -248,20 +272,20 @@ func (entry *Entry) Warningln(args ...interface{}) {
 }
 
 func (entry *Entry) Errorln(args ...interface{}) {
-       if entry.Logger.level() >= ErrorLevel {
+       if entry.Logger.IsLevelEnabled(ErrorLevel) {
                entry.Error(entry.sprintlnn(args...))
        }
 }
 
 func (entry *Entry) Fatalln(args ...interface{}) {
-       if entry.Logger.level() >= FatalLevel {
+       if entry.Logger.IsLevelEnabled(FatalLevel) {
                entry.Fatal(entry.sprintlnn(args...))
        }
        Exit(1)
 }
 
 func (entry *Entry) Panicln(args ...interface{}) {
-       if entry.Logger.level() >= PanicLevel {
+       if entry.Logger.IsLevelEnabled(PanicLevel) {
                entry.Panic(entry.sprintlnn(args...))
        }
 }
index 99c3b41..a81e2b3 100644 (file)
@@ -75,3 +75,41 @@ func TestEntryPanicf(t *testing.T) {
        entry := NewEntry(logger)
        entry.WithField("err", errBoom).Panicf("kaboom %v", true)
 }
+
+const (
+       badMessage   = "this is going to panic"
+       panicMessage = "this is broken"
+)
+
+type panickyHook struct{}
+
+func (p *panickyHook) Levels() []Level {
+       return []Level{InfoLevel}
+}
+
+func (p *panickyHook) Fire(entry *Entry) error {
+       if entry.Message == badMessage {
+               panic(panicMessage)
+       }
+
+       return nil
+}
+
+func TestEntryHooksPanic(t *testing.T) {
+       logger := New()
+       logger.Out = &bytes.Buffer{}
+       logger.Level = InfoLevel
+       logger.Hooks.Add(&panickyHook{})
+
+       defer func() {
+               p := recover()
+               assert.NotNil(t, p)
+               assert.Equal(t, panicMessage, p)
+
+               entry := NewEntry(logger)
+               entry.Info("another message")
+       }()
+
+       entry := NewEntry(logger)
+       entry.Info(badMessage)
+}
index a2acf55..5f3849b 100644 (file)
@@ -1,14 +1,16 @@
 package logrus_test
 
 import (
-       "github.com/sirupsen/logrus"
        "os"
+
+       "github.com/sirupsen/logrus"
 )
 
 func Example_basic() {
        var log = logrus.New()
        log.Formatter = new(logrus.JSONFormatter)
        log.Formatter = new(logrus.TextFormatter)                     //default
+       log.Formatter.(*logrus.TextFormatter).DisableColors = true    // remove colors
        log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
        log.Level = logrus.DebugLevel
        log.Out = os.Stdout
index d4ddffc..15118d2 100644 (file)
@@ -1,16 +1,24 @@
+// +build !windows
+
 package logrus_test
 
 import (
-       "github.com/sirupsen/logrus"
-       "gopkg.in/gemnasium/logrus-airbrake-hook.v2"
+       "log/syslog"
        "os"
+
+       "github.com/sirupsen/logrus"
+       slhooks "github.com/sirupsen/logrus/hooks/syslog"
 )
 
+// An example on how to use a hook
 func Example_hook() {
        var log = logrus.New()
        log.Formatter = new(logrus.TextFormatter)                     // default
+       log.Formatter.(*logrus.TextFormatter).DisableColors = true    // remove colors
        log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
-       log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
+       if sl, err := slhooks.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, ""); err != nil {
+               log.Hooks.Add(sl)
+       }
        log.Out = os.Stdout
 
        log.WithFields(logrus.Fields{
index 013183e..fb2a7a1 100644 (file)
@@ -2,6 +2,7 @@ package logrus
 
 import (
        "io"
+       "time"
 )
 
 var (
@@ -15,37 +16,32 @@ func StandardLogger() *Logger {
 
 // SetOutput sets the standard logger output.
 func SetOutput(out io.Writer) {
-       std.mu.Lock()
-       defer std.mu.Unlock()
-       std.Out = out
+       std.SetOutput(out)
 }
 
 // SetFormatter sets the standard logger formatter.
 func SetFormatter(formatter Formatter) {
-       std.mu.Lock()
-       defer std.mu.Unlock()
-       std.Formatter = formatter
+       std.SetFormatter(formatter)
 }
 
 // SetLevel sets the standard logger level.
 func SetLevel(level Level) {
-       std.mu.Lock()
-       defer std.mu.Unlock()
        std.SetLevel(level)
 }
 
 // GetLevel returns the standard logger level.
 func GetLevel() Level {
-       std.mu.Lock()
-       defer std.mu.Unlock()
-       return std.level()
+       return std.GetLevel()
+}
+
+// IsLevelEnabled checks if the log level of the standard logger is greater than the level param
+func IsLevelEnabled(level Level) bool {
+       return std.IsLevelEnabled(level)
 }
 
 // AddHook adds a hook to the standard logger hooks.
 func AddHook(hook Hook) {
-       std.mu.Lock()
-       defer std.mu.Unlock()
-       std.Hooks.Add(hook)
+       std.AddHook(hook)
 }
 
 // WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
@@ -72,6 +68,15 @@ func WithFields(fields Fields) *Entry {
        return std.WithFields(fields)
 }
 
+// WithTime creats an entry from the standard logger and overrides the time of
+// logs generated with it.
+//
+// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
+// or Panic on the Entry it returns.
+func WithTime(t time.Time) *Entry {
+       return std.WithTime(t)
+}
+
 // Debug logs a message at level Debug on the standard logger.
 func Debug(args ...interface{}) {
        std.Debug(args...)
@@ -107,7 +112,7 @@ func Panic(args ...interface{}) {
        std.Panic(args...)
 }
 
-// Fatal logs a message at level Fatal on the standard logger.
+// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
 func Fatal(args ...interface{}) {
        std.Fatal(args...)
 }
@@ -147,7 +152,7 @@ func Panicf(format string, args ...interface{}) {
        std.Panicf(format, args...)
 }
 
-// Fatalf logs a message at level Fatal on the standard logger.
+// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
 func Fatalf(format string, args ...interface{}) {
        std.Fatalf(format, args...)
 }
@@ -187,7 +192,7 @@ func Panicln(args ...interface{}) {
        std.Panicln(args...)
 }
 
-// Fatalln logs a message at level Fatal on the standard logger.
+// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
 func Fatalln(args ...interface{}) {
        std.Fatalln(args...)
 }
index b183ff5..83c7494 100644 (file)
@@ -30,16 +30,22 @@ type Formatter interface {
 //
 // It's not exported because it's still using Data in an opinionated way. It's to
 // avoid code duplication between the two default formatters.
-func prefixFieldClashes(data Fields) {
-       if t, ok := data["time"]; ok {
-               data["fields.time"] = t
+func prefixFieldClashes(data Fields, fieldMap FieldMap) {
+       timeKey := fieldMap.resolve(FieldKeyTime)
+       if t, ok := data[timeKey]; ok {
+               data["fields."+timeKey] = t
+               delete(data, timeKey)
        }
 
-       if m, ok := data["msg"]; ok {
-               data["fields.msg"] = m
+       msgKey := fieldMap.resolve(FieldKeyMsg)
+       if m, ok := data[msgKey]; ok {
+               data["fields."+msgKey] = m
+               delete(data, msgKey)
        }
 
-       if l, ok := data["level"]; ok {
-               data["fields.level"] = l
+       levelKey := fieldMap.resolve(FieldKeyLevel)
+       if l, ok := data[levelKey]; ok {
+               data["fields."+levelKey] = l
+               delete(data, levelKey)
        }
 }
diff --git a/vendor/github.com/Sirupsen/logrus/go.mod b/vendor/github.com/Sirupsen/logrus/go.mod
new file mode 100644 (file)
index 0000000..f4fed02
--- /dev/null
@@ -0,0 +1,10 @@
+module github.com/sirupsen/logrus
+
+require (
+       github.com/davecgh/go-spew v1.1.1 // indirect
+       github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe
+       github.com/pmezard/go-difflib v1.0.0 // indirect
+       github.com/stretchr/testify v1.2.2
+       golang.org/x/crypto v0.0.0-20180904163835-0709b304e793
+       golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33
+)
diff --git a/vendor/github.com/Sirupsen/logrus/go.sum b/vendor/github.com/Sirupsen/logrus/go.sum
new file mode 100644 (file)
index 0000000..1f0d719
--- /dev/null
@@ -0,0 +1,12 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs=
+github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
index 13f34cb..80b93b8 100644 (file)
@@ -1,9 +1,13 @@
 package logrus
 
 import (
+       "bytes"
+       "encoding/json"
+       "sync"
        "testing"
 
        "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
 )
 
 type TestHook struct {
@@ -84,6 +88,46 @@ func TestCanFireMultipleHooks(t *testing.T) {
        })
 }
 
+type SingleLevelModifyHook struct {
+       ModifyHook
+}
+
+func (h *SingleLevelModifyHook) Levels() []Level {
+       return []Level{InfoLevel}
+}
+
+func TestHookEntryIsPristine(t *testing.T) {
+       l := New()
+       b := &bytes.Buffer{}
+       l.Formatter = &JSONFormatter{}
+       l.Out = b
+       l.AddHook(&SingleLevelModifyHook{})
+
+       l.Error("error message")
+       data := map[string]string{}
+       err := json.Unmarshal(b.Bytes(), &data)
+       require.NoError(t, err)
+       _, ok := data["wow"]
+       require.False(t, ok)
+       b.Reset()
+
+       l.Info("error message")
+       data = map[string]string{}
+       err = json.Unmarshal(b.Bytes(), &data)
+       require.NoError(t, err)
+       _, ok = data["wow"]
+       require.True(t, ok)
+       b.Reset()
+
+       l.Error("error message")
+       data = map[string]string{}
+       err = json.Unmarshal(b.Bytes(), &data)
+       require.NoError(t, err)
+       _, ok = data["wow"]
+       require.False(t, ok)
+       b.Reset()
+}
+
 type ErrorHook struct {
        Fired bool
 }
@@ -120,3 +164,24 @@ func TestErrorHookShouldFireOnError(t *testing.T) {
                assert.Equal(t, hook.Fired, true)
        })
 }
+
+func TestAddHookRace(t *testing.T) {
+       var wg sync.WaitGroup
+       wg.Add(2)
+       hook := new(ErrorHook)
+       LogAndAssertJSON(t, func(log *Logger) {
+               go func() {
+                       defer wg.Done()
+                       log.AddHook(hook)
+               }()
+               go func() {
+                       defer wg.Done()
+                       log.Error("test")
+               }()
+               wg.Wait()
+       }, func(fields Fields) {
+               // the line may have been logged
+               // before the hook was added, so we can't
+               // actually assert on the hook
+       })
+}
index 62c4845..234a17d 100644 (file)
@@ -15,7 +15,7 @@ type Hook struct {
        // Entries is an array of all entries that have been received by this hook.
        // For safe access, use the AllEntries() method, rather than reading this
        // value directly.
-       Entries []*logrus.Entry
+       Entries []logrus.Entry
        mu      sync.RWMutex
 }
 
@@ -52,7 +52,7 @@ func NewNullLogger() (*logrus.Logger, *Hook) {
 func (t *Hook) Fire(e *logrus.Entry) error {
        t.mu.Lock()
        defer t.mu.Unlock()
-       t.Entries = append(t.Entries, e)
+       t.Entries = append(t.Entries, *e)
        return nil
 }
 
@@ -68,9 +68,7 @@ func (t *Hook) LastEntry() *logrus.Entry {
        if i < 0 {
                return nil
        }
-       // Make a copy, for safety
-       e := *t.Entries[i]
-       return &e
+       return &t.Entries[i]
 }
 
 // AllEntries returns all entries that were logged.
@@ -79,10 +77,9 @@ func (t *Hook) AllEntries() []*logrus.Entry {
        defer t.mu.RUnlock()
        // Make a copy so the returned value won't race with future log requests
        entries := make([]*logrus.Entry, len(t.Entries))
-       for i, entry := range t.Entries {
+       for i := 0; i < len(t.Entries); i++ {
                // Make a copy, for safety
-               e := *entry
-               entries[i] = &e
+               entries[i] = &t.Entries[i]
        }
        return entries
 }
@@ -91,5 +88,5 @@ func (t *Hook) AllEntries() []*logrus.Entry {
 func (t *Hook) Reset() {
        t.mu.Lock()
        defer t.mu.Unlock()
-       t.Entries = make([]*logrus.Entry, 0)
+       t.Entries = make([]logrus.Entry, 0)
 }
index 3f55cfe..d6f6d30 100644 (file)
@@ -1,14 +1,16 @@
 package test
 
 import (
+       "math/rand"
+       "sync"
        "testing"
+       "time"
 
        "github.com/sirupsen/logrus"
        "github.com/stretchr/testify/assert"
 )
 
 func TestAllHooks(t *testing.T) {
-
        assert := assert.New(t)
 
        logger, hook := NewNullLogger()
@@ -35,5 +37,37 @@ func TestAllHooks(t *testing.T) {
        assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
        assert.Equal("Hello error", hook.LastEntry().Message)
        assert.Equal(1, len(hook.Entries))
+}
+
+func TestLoggingWithHooksRace(t *testing.T) {
+
+       rand.Seed(time.Now().Unix())
+       unlocker := rand.Int() % 100
+
+       assert := assert.New(t)
+       logger, hook := NewNullLogger()
+
+       var wgOne, wgAll sync.WaitGroup
+       wgOne.Add(1)
+       wgAll.Add(100)
+
+       for i := 0; i < 100; i++ {
+               go func(i int) {
+                       logger.Info("info")
+                       wgAll.Done()
+                       if i == unlocker {
+                               wgOne.Done()
+                       }
+               }(i)
+       }
+
+       wgOne.Wait()
+
+       assert.Equal(logrus.InfoLevel, hook.LastEntry().Level)
+       assert.Equal("info", hook.LastEntry().Message)
+
+       wgAll.Wait()
 
+       entries := hook.AllEntries()
+       assert.Equal(100, len(entries))
 }
index fb01c1b..d3dadef 100644 (file)
@@ -1,6 +1,7 @@
 package logrus
 
 import (
+       "bytes"
        "encoding/json"
        "fmt"
 )
@@ -33,6 +34,9 @@ type JSONFormatter struct {
        // DisableTimestamp allows disabling automatic timestamps in output
        DisableTimestamp bool
 
+       // DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
+       DataKey string
+
        // FieldMap allows users to customize the names of keys for default fields.
        // As an example:
        // formatter := &JSONFormatter{
@@ -43,6 +47,9 @@ type JSONFormatter struct {
        //    },
        // }
        FieldMap FieldMap
+
+       // PrettyPrint will indent all json logs
+       PrettyPrint bool
 }
 
 // Format renders a single log entry
@@ -58,7 +65,14 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
                        data[k] = v
                }
        }
-       prefixFieldClashes(data)
+
+       if f.DataKey != "" {
+               newData := make(Fields, 4)
+               newData[f.DataKey] = data
+               data = newData
+       }
+
+       prefixFieldClashes(data, f.FieldMap)
 
        timestampFormat := f.TimestampFormat
        if timestampFormat == "" {
@@ -71,9 +85,20 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
        data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
        data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
 
-       serialized, err := json.Marshal(data)
-       if err != nil {
+       var b *bytes.Buffer
+       if entry.Buffer != nil {
+               b = entry.Buffer
+       } else {
+               b = &bytes.Buffer{}
+       }
+
+       encoder := json.NewEncoder(b)
+       if f.PrettyPrint {
+               encoder.SetIndent("", "  ")
+       }
+       if err := encoder.Encode(data); err != nil {
                return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
        }
-       return append(serialized, '\n'), nil
+
+       return b.Bytes(), nil
 }
index 51093a7..0dde300 100644 (file)
@@ -3,6 +3,7 @@ package logrus
 import (
        "encoding/json"
        "errors"
+       "fmt"
        "strings"
        "testing"
 )
@@ -106,6 +107,102 @@ func TestFieldClashWithLevel(t *testing.T) {
        }
 }
 
+func TestFieldClashWithRemappedFields(t *testing.T) {
+       formatter := &JSONFormatter{
+               FieldMap: FieldMap{
+                       FieldKeyTime:  "@timestamp",
+                       FieldKeyLevel: "@level",
+                       FieldKeyMsg:   "@message",
+               },
+       }
+
+       b, err := formatter.Format(WithFields(Fields{
+               "@timestamp": "@timestamp",
+               "@level":     "@level",
+               "@message":   "@message",
+               "timestamp":  "timestamp",
+               "level":      "level",
+               "msg":        "msg",
+       }))
+       if err != nil {
+               t.Fatal("Unable to format entry: ", err)
+       }
+
+       entry := make(map[string]interface{})
+       err = json.Unmarshal(b, &entry)
+       if err != nil {
+               t.Fatal("Unable to unmarshal formatted entry: ", err)
+       }
+
+       for _, field := range []string{"timestamp", "level", "msg"} {
+               if entry[field] != field {
+                       t.Errorf("Expected field %v to be untouched; got %v", field, entry[field])
+               }
+
+               remappedKey := fmt.Sprintf("fields.%s", field)
+               if remapped, ok := entry[remappedKey]; ok {
+                       t.Errorf("Expected %s to be empty; got %v", remappedKey, remapped)
+               }
+       }
+
+       for _, field := range []string{"@timestamp", "@level", "@message"} {
+               if entry[field] == field {
+                       t.Errorf("Expected field %v to be mapped to an Entry value", field)
+               }
+
+               remappedKey := fmt.Sprintf("fields.%s", field)
+               if remapped, ok := entry[remappedKey]; ok {
+                       if remapped != field {
+                               t.Errorf("Expected field %v to be copied to %s; got %v", field, remappedKey, remapped)
+                       }
+               } else {
+                       t.Errorf("Expected field %v to be copied to %s; was absent", field, remappedKey)
+               }
+       }
+}
+
+func TestFieldsInNestedDictionary(t *testing.T) {
+       formatter := &JSONFormatter{
+               DataKey: "args",
+       }
+
+       logEntry := WithFields(Fields{
+               "level":      "level",
+               "test":           "test",
+       })
+       logEntry.Level = InfoLevel
+
+       b, err := formatter.Format(logEntry)
+       if err != nil {
+               t.Fatal("Unable to format entry: ", err)
+       }
+
+       entry := make(map[string]interface{})
+       err = json.Unmarshal(b, &entry)
+       if err != nil {
+               t.Fatal("Unable to unmarshal formatted entry: ", err)
+       }
+
+       args := entry["args"].(map[string]interface{})
+
+       for _, field := range []string{"test", "level"} {
+               if value, present := args[field]; !present || value != field {
+                       t.Errorf("Expected field %v to be present under 'args'; untouched", field)
+               }
+       }
+
+       for _, field := range []string{"test", "fields.level"} {
+               if _, present := entry[field]; present {
+                       t.Errorf("Expected field %v not to be present at top level", field)
+               }
+       }
+
+       // with nested object, "level" shouldn't clash
+       if entry["level"] != "info" {
+               t.Errorf("Expected 'level' field to contain 'info'")
+       }
+}
+
 func TestJSONEntryEndsWithNewline(t *testing.T) {
        formatter := &JSONFormatter{}
 
index 2acab05..b67bfcb 100644 (file)
@@ -5,12 +5,13 @@ import (
        "os"
        "sync"
        "sync/atomic"
+       "time"
 )
 
 type Logger struct {
        // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
        // file, or leave it default which is `os.Stderr`. You can also set this to
-       // something more adventorous, such as logging to Kafka.
+       // something more adventurous, such as logging to Kafka.
        Out io.Writer
        // Hooks for the logger instance. These allow firing events based on logging
        // levels and log entries. For example, to send errors to an error tracking
@@ -84,11 +85,12 @@ func (logger *Logger) newEntry() *Entry {
 }
 
 func (logger *Logger) releaseEntry(entry *Entry) {
+       entry.Data = map[string]interface{}{}
        logger.entryPool.Put(entry)
 }
 
 // Adds a field to the log entry, note that it doesn't log until you call
-// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
+// Debug, Print, Info, Warn, Error, Fatal or Panic. It only creates a log entry.
 // If you want multiple fields, use `WithFields`.
 func (logger *Logger) WithField(key string, value interface{}) *Entry {
        entry := logger.newEntry()
@@ -112,8 +114,15 @@ func (logger *Logger) WithError(err error) *Entry {
        return entry.WithError(err)
 }
 
+// Overrides the time of the log entry.
+func (logger *Logger) WithTime(t time.Time) *Entry {
+       entry := logger.newEntry()
+       defer logger.releaseEntry(entry)
+       return entry.WithTime(t)
+}
+
 func (logger *Logger) Debugf(format string, args ...interface{}) {
-       if logger.level() >= DebugLevel {
+       if logger.IsLevelEnabled(DebugLevel) {
                entry := logger.newEntry()
                entry.Debugf(format, args...)
                logger.releaseEntry(entry)
@@ -121,7 +130,7 @@ func (logger *Logger) Debugf(format string, args ...interface{}) {
 }
 
 func (logger *Logger) Infof(format string, args ...interface{}) {
-       if logger.level() >= InfoLevel {
+       if logger.IsLevelEnabled(InfoLevel) {
                entry := logger.newEntry()
                entry.Infof(format, args...)
                logger.releaseEntry(entry)
@@ -135,7 +144,7 @@ func (logger *Logger) Printf(format string, args ...interface{}) {
 }
 
 func (logger *Logger) Warnf(format string, args ...interface{}) {
-       if logger.level() >= WarnLevel {
+       if logger.IsLevelEnabled(WarnLevel) {
                entry := logger.newEntry()
                entry.Warnf(format, args...)
                logger.releaseEntry(entry)
@@ -143,7 +152,7 @@ func (logger *Logger) Warnf(format string, args ...interface{}) {
 }
 
 func (logger *Logger) Warningf(format string, args ...interface{}) {
-       if logger.level() >= WarnLevel {
+       if logger.IsLevelEnabled(WarnLevel) {
                entry := logger.newEntry()
                entry.Warnf(format, args...)
                logger.releaseEntry(entry)
@@ -151,7 +160,7 @@ func (logger *Logger) Warningf(format string, args ...interface{}) {
 }
 
 func (logger *Logger) Errorf(format string, args ...interface{}) {
-       if logger.level() >= ErrorLevel {
+       if logger.IsLevelEnabled(ErrorLevel) {
                entry := logger.newEntry()
                entry.Errorf(format, args...)
                logger.releaseEntry(entry)
@@ -159,7 +168,7 @@ func (logger *Logger) Errorf(format string, args ...interface{}) {
 }
 
 func (logger *Logger) Fatalf(format string, args ...interface{}) {
-       if logger.level() >= FatalLevel {
+       if logger.IsLevelEnabled(FatalLevel) {
                entry := logger.newEntry()
                entry.Fatalf(format, args...)
                logger.releaseEntry(entry)
@@ -168,7 +177,7 @@ func (logger *Logger) Fatalf(format string, args ...interface{}) {
 }
 
 func (logger *Logger) Panicf(format string, args ...interface{}) {
-       if logger.level() >= PanicLevel {
+       if logger.IsLevelEnabled(PanicLevel) {
                entry := logger.newEntry()
                entry.Panicf(format, args...)
                logger.releaseEntry(entry)
@@ -176,7 +185,7 @@ func (logger *Logger) Panicf(format string, args ...interface{}) {
 }
 
 func (logger *Logger) Debug(args ...interface{}) {
-       if logger.level() >= DebugLevel {
+       if logger.IsLevelEnabled(DebugLevel) {
                entry := logger.newEntry()
                entry.Debug(args...)
                logger.releaseEntry(entry)
@@ -184,7 +193,7 @@ func (logger *Logger) Debug(args ...interface{}) {
 }
 
 func (logger *Logger) Info(args ...interface{}) {
-       if logger.level() >= InfoLevel {
+       if logger.IsLevelEnabled(InfoLevel) {
                entry := logger.newEntry()
                entry.Info(args...)
                logger.releaseEntry(entry)
@@ -198,7 +207,7 @@ func (logger *Logger) Print(args ...interface{}) {
 }
 
 func (logger *Logger) Warn(args ...interface{}) {
-       if logger.level() >= WarnLevel {
+       if logger.IsLevelEnabled(WarnLevel) {
                entry := logger.newEntry()
                entry.Warn(args...)
                logger.releaseEntry(entry)
@@ -206,7 +215,7 @@ func (logger *Logger) Warn(args ...interface{}) {
 }
 
 func (logger *Logger) Warning(args ...interface{}) {
-       if logger.level() >= WarnLevel {
+       if logger.IsLevelEnabled(WarnLevel) {
                entry := logger.newEntry()
                entry.Warn(args...)
                logger.releaseEntry(entry)
@@ -214,7 +223,7 @@ func (logger *Logger) Warning(args ...interface{}) {
 }
 
 func (logger *Logger) Error(args ...interface{}) {
-       if logger.level() >= ErrorLevel {
+       if logger.IsLevelEnabled(ErrorLevel) {
                entry := logger.newEntry()
                entry.Error(args...)
                logger.releaseEntry(entry)
@@ -222,7 +231,7 @@ func (logger *Logger) Error(args ...interface{}) {
 }
 
 func (logger *Logger) Fatal(args ...interface{}) {
-       if logger.level() >= FatalLevel {
+       if logger.IsLevelEnabled(FatalLevel) {
                entry := logger.newEntry()
                entry.Fatal(args...)
                logger.releaseEntry(entry)
@@ -231,7 +240,7 @@ func (logger *Logger) Fatal(args ...interface{}) {
 }
 
 func (logger *Logger) Panic(args ...interface{}) {
-       if logger.level() >= PanicLevel {
+       if logger.IsLevelEnabled(PanicLevel) {
                entry := logger.newEntry()
                entry.Panic(args...)
                logger.releaseEntry(entry)
@@ -239,7 +248,7 @@ func (logger *Logger) Panic(args ...interface{}) {
 }
 
 func (logger *Logger) Debugln(args ...interface{}) {
-       if logger.level() >= DebugLevel {
+       if logger.IsLevelEnabled(DebugLevel) {
                entry := logger.newEntry()
                entry.Debugln(args...)
                logger.releaseEntry(entry)
@@ -247,7 +256,7 @@ func (logger *Logger) Debugln(args ...interface{}) {
 }
 
 func (logger *Logger) Infoln(args ...interface{}) {
-       if logger.level() >= InfoLevel {
+       if logger.IsLevelEnabled(InfoLevel) {
                entry := logger.newEntry()
                entry.Infoln(args...)
                logger.releaseEntry(entry)
@@ -261,7 +270,7 @@ func (logger *Logger) Println(args ...interface{}) {
 }
 
 func (logger *Logger) Warnln(args ...interface{}) {
-       if logger.level() >= WarnLevel {
+       if logger.IsLevelEnabled(WarnLevel) {
                entry := logger.newEntry()
                entry.Warnln(args...)
                logger.releaseEntry(entry)
@@ -269,7 +278,7 @@ func (logger *Logger) Warnln(args ...interface{}) {
 }
 
 func (logger *Logger) Warningln(args ...interface{}) {
-       if logger.level() >= WarnLevel {
+       if logger.IsLevelEnabled(WarnLevel) {
                entry := logger.newEntry()
                entry.Warnln(args...)
                logger.releaseEntry(entry)
@@ -277,7 +286,7 @@ func (logger *Logger) Warningln(args ...interface{}) {
 }
 
 func (logger *Logger) Errorln(args ...interface{}) {
-       if logger.level() >= ErrorLevel {
+       if logger.IsLevelEnabled(ErrorLevel) {
                entry := logger.newEntry()
                entry.Errorln(args...)
                logger.releaseEntry(entry)
@@ -285,7 +294,7 @@ func (logger *Logger) Errorln(args ...interface{}) {
 }
 
 func (logger *Logger) Fatalln(args ...interface{}) {
-       if logger.level() >= FatalLevel {
+       if logger.IsLevelEnabled(FatalLevel) {
                entry := logger.newEntry()
                entry.Fatalln(args...)
                logger.releaseEntry(entry)
@@ -294,7 +303,7 @@ func (logger *Logger) Fatalln(args ...interface{}) {
 }
 
 func (logger *Logger) Panicln(args ...interface{}) {
-       if logger.level() >= PanicLevel {
+       if logger.IsLevelEnabled(PanicLevel) {
                entry := logger.newEntry()
                entry.Panicln(args...)
                logger.releaseEntry(entry)
@@ -312,6 +321,47 @@ func (logger *Logger) level() Level {
        return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
 }
 
+// SetLevel sets the logger level.
 func (logger *Logger) SetLevel(level Level) {
        atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
 }
+
+// GetLevel returns the logger level.
+func (logger *Logger) GetLevel() Level {
+       return logger.level()
+}
+
+// AddHook adds a hook to the logger hooks.
+func (logger *Logger) AddHook(hook Hook) {
+       logger.mu.Lock()
+       defer logger.mu.Unlock()
+       logger.Hooks.Add(hook)
+}
+
+// IsLevelEnabled checks if the log level of the logger is greater than the level param
+func (logger *Logger) IsLevelEnabled(level Level) bool {
+       return logger.level() >= level
+}
+
+// SetFormatter sets the logger formatter.
+func (logger *Logger) SetFormatter(formatter Formatter) {
+       logger.mu.Lock()
+       defer logger.mu.Unlock()
+       logger.Formatter = formatter
+}
+
+// SetOutput sets the logger output.
+func (logger *Logger) SetOutput(output io.Writer) {
+       logger.mu.Lock()
+       defer logger.mu.Unlock()
+       logger.Out = output
+}
+
+// ReplaceHooks replaces the logger hooks and returns the old ones
+func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks {
+       logger.mu.Lock()
+       oldHooks := logger.Hooks
+       logger.Hooks = hooks
+       logger.mu.Unlock()
+       return oldHooks
+}
index dd23a35..f0a7684 100644 (file)
@@ -1,6 +1,7 @@
 package logrus
 
 import (
+       "io/ioutil"
        "os"
        "testing"
 )
@@ -59,3 +60,26 @@ func doLoggerBenchmarkNoLock(b *testing.B, out *os.File, formatter Formatter, fi
                }
        })
 }
+
+func BenchmarkLoggerJSONFormatter(b *testing.B) {
+       doLoggerBenchmarkWithFormatter(b, &JSONFormatter{})
+}
+
+func BenchmarkLoggerTextFormatter(b *testing.B) {
+       doLoggerBenchmarkWithFormatter(b, &TextFormatter{})
+}
+
+func doLoggerBenchmarkWithFormatter(b *testing.B, f Formatter) {
+       b.SetParallelism(100)
+       log := New()
+       log.Formatter = f
+       log.Out = ioutil.Discard
+       b.RunParallel(func(pb *testing.PB) {
+               for pb.Next() {
+                       log.
+                               WithField("foo1", "bar1").
+                               WithField("foo2", "bar2").
+                               Info("this is a dummy log")
+               }
+       })
+}
index dd38999..fa0b9de 100644 (file)
@@ -140,4 +140,11 @@ type FieldLogger interface {
        Errorln(args ...interface{})
        Fatalln(args ...interface{})
        Panicln(args ...interface{})
+
+       // IsDebugEnabled() bool
+       // IsInfoEnabled() bool
+       // IsWarnEnabled() bool
+       // IsErrorEnabled() bool
+       // IsFatalEnabled() bool
+       // IsPanicEnabled() bool
 }
index 78cbc28..97d15d7 100644 (file)
@@ -3,10 +3,12 @@ package logrus
 import (
        "bytes"
        "encoding/json"
+       "io/ioutil"
        "strconv"
        "strings"
        "sync"
        "testing"
+       "time"
 
        "github.com/stretchr/testify/assert"
 )
@@ -209,6 +211,65 @@ func TestDefaultFieldsAreNotPrefixed(t *testing.T) {
        })
 }
 
+func TestWithTimeShouldOverrideTime(t *testing.T) {
+       now := time.Now().Add(24 * time.Hour)
+
+       LogAndAssertJSON(t, func(log *Logger) {
+               log.WithTime(now).Info("foobar")
+       }, func(fields Fields) {
+               assert.Equal(t, fields["time"], now.Format(defaultTimestampFormat))
+       })
+}
+
+func TestWithTimeShouldNotOverrideFields(t *testing.T) {
+       now := time.Now().Add(24 * time.Hour)
+
+       LogAndAssertJSON(t, func(log *Logger) {
+               log.WithField("herp", "derp").WithTime(now).Info("blah")
+       }, func(fields Fields) {
+               assert.Equal(t, fields["time"], now.Format(defaultTimestampFormat))
+               assert.Equal(t, fields["herp"], "derp")
+       })
+}
+
+func TestWithFieldShouldNotOverrideTime(t *testing.T) {
+       now := time.Now().Add(24 * time.Hour)
+
+       LogAndAssertJSON(t, func(log *Logger) {
+               log.WithTime(now).WithField("herp", "derp").Info("blah")
+       }, func(fields Fields) {
+               assert.Equal(t, fields["time"], now.Format(defaultTimestampFormat))
+               assert.Equal(t, fields["herp"], "derp")
+       })
+}
+
+func TestTimeOverrideMultipleLogs(t *testing.T) {
+       var buffer bytes.Buffer
+       var firstFields, secondFields Fields
+
+       logger := New()
+       logger.Out = &buffer
+       formatter := new(JSONFormatter)
+       formatter.TimestampFormat = time.StampMilli
+       logger.Formatter = formatter
+
+       llog := logger.WithField("herp", "derp")
+       llog.Info("foo")
+
+       err := json.Unmarshal(buffer.Bytes(), &firstFields)
+       assert.NoError(t, err, "should have decoded first message")
+
+       buffer.Reset()
+
+       time.Sleep(10 * time.Millisecond)
+       llog.Info("bar")
+
+       err = json.Unmarshal(buffer.Bytes(), &secondFields)
+       assert.NoError(t, err, "should have decoded second message")
+
+       assert.NotEqual(t, firstFields["time"], secondFields["time"], "timestamps should not be equal")
+}
+
 func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
 
        var buffer bytes.Buffer
@@ -343,6 +404,45 @@ func TestLoggingRace(t *testing.T) {
        wg.Wait()
 }
 
+func TestLoggingRaceWithHooksOnEntry(t *testing.T) {
+       logger := New()
+       hook := new(ModifyHook)
+       logger.AddHook(hook)
+       entry := logger.WithField("context", "clue")
+
+       var wg sync.WaitGroup
+       wg.Add(100)
+
+       for i := 0; i < 100; i++ {
+               go func() {
+                       entry.Info("info")
+                       wg.Done()
+               }()
+       }
+       wg.Wait()
+}
+
+func TestReplaceHooks(t *testing.T) {
+       old, cur := &TestHook{}, &TestHook{}
+
+       logger := New()
+       logger.SetOutput(ioutil.Discard)
+       logger.AddHook(old)
+
+       hooks := make(LevelHooks)
+       hooks.Add(cur)
+       replaced := logger.ReplaceHooks(hooks)
+
+       logger.Info("test")
+
+       assert.Equal(t, old.Fired, false)
+       assert.Equal(t, cur.Fired, true)
+
+       logger.ReplaceHooks(replaced)
+       logger.Info("test")
+       assert.Equal(t, old.Fired, true)
+}
+
 // Compile test
 func TestLogrusInterface(t *testing.T) {
        var buffer bytes.Buffer
@@ -384,3 +484,54 @@ func TestEntryWriter(t *testing.T) {
        assert.Equal(t, fields["foo"], "bar")
        assert.Equal(t, fields["level"], "warning")
 }
+
+func TestLogLevelEnabled(t *testing.T) {
+       log := New()
+       log.SetLevel(PanicLevel)
+       assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(FatalLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(ErrorLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(WarnLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(InfoLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
+
+       log.SetLevel(FatalLevel)
+       assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(FatalLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(ErrorLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(WarnLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(InfoLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
+
+       log.SetLevel(ErrorLevel)
+       assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(FatalLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(WarnLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(InfoLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
+
+       log.SetLevel(WarnLevel)
+       assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(FatalLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(WarnLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(InfoLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
+
+       log.SetLevel(InfoLevel)
+       assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(FatalLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(WarnLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(InfoLevel))
+       assert.Equal(t, false, log.IsLevelEnabled(DebugLevel))
+
+       log.SetLevel(DebugLevel)
+       assert.Equal(t, true, log.IsLevelEnabled(PanicLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(FatalLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(ErrorLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(WarnLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(InfoLevel))
+       assert.Equal(t, true, log.IsLevelEnabled(DebugLevel))
+}
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_appengine.go b/vendor/github.com/Sirupsen/logrus/terminal_appengine.go
new file mode 100644 (file)
index 0000000..72f679c
--- /dev/null
@@ -0,0 +1,13 @@
+// Based on ssh/terminal:
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build appengine
+
+package logrus
+
+import "io"
+
+func initTerminal(w io.Writer) {
+}
index d7b3893..62ca252 100644 (file)
@@ -1,10 +1,17 @@
 // +build darwin freebsd openbsd netbsd dragonfly
-// +build !appengine
+// +build !appengine,!js
 
 package logrus
 
-import "golang.org/x/sys/unix"
+import (
+       "io"
+
+       "golang.org/x/sys/unix"
+)
 
 const ioctlReadTermios = unix.TIOCGETA
 
 type Termios unix.Termios
+
+func initTerminal(w io.Writer) {
+}
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go b/vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go
new file mode 100644 (file)
index 0000000..2403de9
--- /dev/null
@@ -0,0 +1,11 @@
+// +build appengine
+
+package logrus
+
+import (
+       "io"
+)
+
+func checkIfTerminal(w io.Writer) bool {
+       return true
+}
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_check_js.go b/vendor/github.com/Sirupsen/logrus/terminal_check_js.go
new file mode 100644 (file)
index 0000000..0c20975
--- /dev/null
@@ -0,0 +1,11 @@
+// +build js
+
+package logrus
+
+import (
+       "io"
+)
+
+func checkIfTerminal(w io.Writer) bool {
+       return false
+}
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go b/vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go
new file mode 100644 (file)
index 0000000..cf309d6
--- /dev/null
@@ -0,0 +1,19 @@
+// +build !appengine,!js,!windows
+
+package logrus
+
+import (
+       "io"
+       "os"
+
+       "golang.org/x/crypto/ssh/terminal"
+)
+
+func checkIfTerminal(w io.Writer) bool {
+       switch v := w.(type) {
+       case *os.File:
+               return terminal.IsTerminal(int(v.Fd()))
+       default:
+               return false
+       }
+}
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_check_windows.go b/vendor/github.com/Sirupsen/logrus/terminal_check_windows.go
new file mode 100644 (file)
index 0000000..3b9d286
--- /dev/null
@@ -0,0 +1,20 @@
+// +build !appengine,!js,windows
+
+package logrus
+
+import (
+       "io"
+       "os"
+       "syscall"
+)
+
+func checkIfTerminal(w io.Writer) bool {
+       switch v := w.(type) {
+       case *os.File:
+               var mode uint32
+               err := syscall.GetConsoleMode(syscall.Handle(v.Fd()), &mode)
+               return err == nil
+       default:
+               return false
+       }
+}
index 88d7298..18066f0 100644 (file)
@@ -3,12 +3,19 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// +build !appengine
+// +build !appengine,!js
 
 package logrus
 
-import "golang.org/x/sys/unix"
+import (
+       "io"
+
+       "golang.org/x/sys/unix"
+)
 
 const ioctlReadTermios = unix.TCGETS
 
 type Termios unix.Termios
+
+func initTerminal(w io.Writer) {
+}
diff --git a/vendor/github.com/Sirupsen/logrus/terminal_windows.go b/vendor/github.com/Sirupsen/logrus/terminal_windows.go
new file mode 100644 (file)
index 0000000..b4ef528
--- /dev/null
@@ -0,0 +1,18 @@
+// +build !appengine,!js,windows
+
+package logrus
+
+import (
+       "io"
+       "os"
+       "syscall"
+
+       sequences "github.com/konsorten/go-windows-terminal-sequences"
+)
+
+func initTerminal(w io.Writer) {
+       switch v := w.(type) {
+       case *os.File:
+               sequences.EnableVirtualTerminalProcessing(syscall.Handle(v.Fd()), true)
+       }
+}
index be412aa..67fb686 100644 (file)
@@ -3,14 +3,11 @@ package logrus
 import (
        "bytes"
        "fmt"
-       "io"
        "os"
        "sort"
        "strings"
        "sync"
        "time"
-
-       "golang.org/x/crypto/ssh/terminal"
 )
 
 const (
@@ -24,6 +21,7 @@ const (
 
 var (
        baseTimestamp time.Time
+       emptyFieldMap FieldMap
 )
 
 func init() {
@@ -38,6 +36,9 @@ type TextFormatter struct {
        // Force disabling colors.
        DisableColors bool
 
+       // Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
+       EnvironmentOverrideColors bool
+
        // Disable timestamp logging. useful when output is redirected to logging
        // system that already adds timestamps.
        DisableTimestamp bool
@@ -54,69 +55,119 @@ type TextFormatter struct {
        // be desired.
        DisableSorting bool
 
+       // The keys sorting function, when uninitialized it uses sort.Strings.
+       SortingFunc func([]string)
+
+       // Disables the truncation of the level text to 4 characters.
+       DisableLevelTruncation bool
+
        // QuoteEmptyFields will wrap empty fields in quotes if true
        QuoteEmptyFields bool
 
        // Whether the logger's out is to a terminal
        isTerminal bool
 
-       sync.Once
+       // FieldMap allows users to customize the names of keys for default fields.
+       // As an example:
+       // formatter := &TextFormatter{
+       //     FieldMap: FieldMap{
+       //         FieldKeyTime:  "@timestamp",
+       //         FieldKeyLevel: "@level",
+       //         FieldKeyMsg:   "@message"}}
+       FieldMap FieldMap
+
+       terminalInitOnce sync.Once
 }
 
 func (f *TextFormatter) init(entry *Entry) {
        if entry.Logger != nil {
-               f.isTerminal = f.checkIfTerminal(entry.Logger.Out)
+               f.isTerminal = checkIfTerminal(entry.Logger.Out)
+
+               if f.isTerminal {
+                       initTerminal(entry.Logger.Out)
+               }
        }
 }
 
-func (f *TextFormatter) checkIfTerminal(w io.Writer) bool {
-       switch v := w.(type) {
-       case *os.File:
-               return terminal.IsTerminal(int(v.Fd()))
-       default:
-               return false
+func (f *TextFormatter) isColored() bool {
+       isColored := f.ForceColors || f.isTerminal
+
+       if f.EnvironmentOverrideColors {
+               if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" {
+                       isColored = true
+               } else if ok && force == "0" {
+                       isColored = false
+               } else if os.Getenv("CLICOLOR") == "0" {
+                       isColored = false
+               }
        }
+
+       return isColored && !f.DisableColors
 }
 
 // Format renders a single log entry
 func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
-       var b *bytes.Buffer
+       prefixFieldClashes(entry.Data, f.FieldMap)
+
        keys := make([]string, 0, len(entry.Data))
        for k := range entry.Data {
                keys = append(keys, k)
        }
 
+       fixedKeys := make([]string, 0, 3+len(entry.Data))
+       if !f.DisableTimestamp {
+               fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
+       }
+       fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel))
+       if entry.Message != "" {
+               fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg))
+       }
+
        if !f.DisableSorting {
-               sort.Strings(keys)
+               if f.SortingFunc == nil {
+                       sort.Strings(keys)
+                       fixedKeys = append(fixedKeys, keys...)
+               } else {
+                       if !f.isColored() {
+                               fixedKeys = append(fixedKeys, keys...)
+                               f.SortingFunc(fixedKeys)
+                       } else {
+                               f.SortingFunc(keys)
+                       }
+               }
+       } else {
+               fixedKeys = append(fixedKeys, keys...)
        }
+
+       var b *bytes.Buffer
        if entry.Buffer != nil {
                b = entry.Buffer
        } else {
                b = &bytes.Buffer{}
        }
 
-       prefixFieldClashes(entry.Data)
-
-       f.Do(func() { f.init(entry) })
-
-       isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors
+       f.terminalInitOnce.Do(func() { f.init(entry) })
 
        timestampFormat := f.TimestampFormat
        if timestampFormat == "" {
                timestampFormat = defaultTimestampFormat
        }
-       if isColored {
+       if f.isColored() {
                f.printColored(b, entry, keys, timestampFormat)
        } else {
-               if !f.DisableTimestamp {
-                       f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
-               }
-               f.appendKeyValue(b, "level", entry.Level.String())
-               if entry.Message != "" {
-                       f.appendKeyValue(b, "msg", entry.Message)
-               }
-               for _, key := range keys {
-                       f.appendKeyValue(b, key, entry.Data[key])
+               for _, key := range fixedKeys {
+                       var value interface{}
+                       switch key {
+                       case f.FieldMap.resolve(FieldKeyTime):
+                               value = entry.Time.Format(timestampFormat)
+                       case f.FieldMap.resolve(FieldKeyLevel):
+                               value = entry.Level.String()
+                       case f.FieldMap.resolve(FieldKeyMsg):
+                               value = entry.Message
+                       default:
+                               value = entry.Data[key]
+                       }
+                       f.appendKeyValue(b, key, value)
                }
        }
 
@@ -137,7 +188,14 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
                levelColor = blue
        }
 
-       levelText := strings.ToUpper(entry.Level.String())[0:4]
+       levelText := strings.ToUpper(entry.Level.String())
+       if !f.DisableLevelTruncation {
+               levelText = levelText[0:4]
+       }
+
+       // Remove a single newline if it already exists in the message to keep
+       // the behavior of logrus text_formatter the same as the stdlib log package
+       entry.Message = strings.TrimSuffix(entry.Message, "\n")
 
        if f.DisableTimestamp {
                fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message)
index d93b931..b0d3a91 100644 (file)
@@ -4,9 +4,14 @@ import (
        "bytes"
        "errors"
        "fmt"
+       "os"
+       "sort"
        "strings"
        "testing"
        "time"
+
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
 )
 
 func TestFormatting(t *testing.T) {
@@ -128,6 +133,44 @@ func TestTimestampFormat(t *testing.T) {
        checkTimeStr("")
 }
 
+func TestDisableLevelTruncation(t *testing.T) {
+       entry := &Entry{
+               Time:    time.Now(),
+               Message: "testing",
+       }
+       keys := []string{}
+       timestampFormat := "Mon Jan 2 15:04:05 -0700 MST 2006"
+       checkDisableTruncation := func(disabled bool, level Level) {
+               tf := &TextFormatter{DisableLevelTruncation: disabled}
+               var b bytes.Buffer
+               entry.Level = level
+               tf.printColored(&b, entry, keys, timestampFormat)
+               logLine := (&b).String()
+               if disabled {
+                       expected := strings.ToUpper(level.String())
+                       if !strings.Contains(logLine, expected) {
+                               t.Errorf("level string expected to be %s when truncation disabled", expected)
+                       }
+               } else {
+                       expected := strings.ToUpper(level.String())
+                       if len(level.String()) > 4 {
+                               if strings.Contains(logLine, expected) {
+                                       t.Errorf("level string %s expected to be truncated to %s when truncation is enabled", expected, expected[0:4])
+                               }
+                       } else {
+                               if !strings.Contains(logLine, expected) {
+                                       t.Errorf("level string expected to be %s when truncation is enabled and level string is below truncation threshold", expected)
+                               }
+                       }
+               }
+       }
+
+       checkDisableTruncation(true, DebugLevel)
+       checkDisableTruncation(true, InfoLevel)
+       checkDisableTruncation(false, ErrorLevel)
+       checkDisableTruncation(false, InfoLevel)
+}
+
 func TestDisableTimestampWithColoredOutput(t *testing.T) {
        tf := &TextFormatter{DisableTimestamp: true, ForceColors: true}
 
@@ -137,5 +180,301 @@ func TestDisableTimestampWithColoredOutput(t *testing.T) {
        }
 }
 
-// TODO add tests for sorting etc., this requires a parser for the text
-// formatter output.
+func TestNewlineBehavior(t *testing.T) {
+       tf := &TextFormatter{ForceColors: true}
+
+       // Ensure a single new line is removed as per stdlib log
+       e := NewEntry(StandardLogger())
+       e.Message = "test message\n"
+       b, _ := tf.Format(e)
+       if bytes.Contains(b, []byte("test message\n")) {
+               t.Error("first newline at end of Entry.Message resulted in unexpected 2 newlines in output. Expected newline to be removed.")
+       }
+
+       // Ensure a double new line is reduced to a single new line
+       e = NewEntry(StandardLogger())
+       e.Message = "test message\n\n"
+       b, _ = tf.Format(e)
+       if bytes.Contains(b, []byte("test message\n\n")) {
+               t.Error("Double newline at end of Entry.Message resulted in unexpected 2 newlines in output. Expected single newline")
+       }
+       if !bytes.Contains(b, []byte("test message\n")) {
+               t.Error("Double newline at end of Entry.Message did not result in a single newline after formatting")
+       }
+}
+
+func TestTextFormatterFieldMap(t *testing.T) {
+       formatter := &TextFormatter{
+               DisableColors: true,
+               FieldMap: FieldMap{
+                       FieldKeyMsg:   "message",
+                       FieldKeyLevel: "somelevel",
+                       FieldKeyTime:  "timeywimey",
+               },
+       }
+
+       entry := &Entry{
+               Message: "oh hi",
+               Level:   WarnLevel,
+               Time:    time.Date(1981, time.February, 24, 4, 28, 3, 100, time.UTC),
+               Data: Fields{
+                       "field1":     "f1",
+                       "message":    "messagefield",
+                       "somelevel":  "levelfield",
+                       "timeywimey": "timeywimeyfield",
+               },
+       }
+
+       b, err := formatter.Format(entry)
+       if err != nil {
+               t.Fatal("Unable to format entry: ", err)
+       }
+
+       assert.Equal(t,
+               `timeywimey="1981-02-24T04:28:03Z" `+
+                       `somelevel=warning `+
+                       `message="oh hi" `+
+                       `field1=f1 `+
+                       `fields.message=messagefield `+
+                       `fields.somelevel=levelfield `+
+                       `fields.timeywimey=timeywimeyfield`+"\n",
+               string(b),
+               "Formatted output doesn't respect FieldMap")
+}
+
+func TestTextFormatterIsColored(t *testing.T) {
+       params := []struct {
+               name               string
+               expectedResult     bool
+               isTerminal         bool
+               disableColor       bool
+               forceColor         bool
+               envColor           bool
+               clicolorIsSet      bool
+               clicolorForceIsSet bool
+               clicolorVal        string
+               clicolorForceVal   string
+       }{
+               // Default values
+               {
+                       name:               "testcase1",
+                       expectedResult:     false,
+                       isTerminal:         false,
+                       disableColor:       false,
+                       forceColor:         false,
+                       envColor:           false,
+                       clicolorIsSet:      false,
+                       clicolorForceIsSet: false,
+               },
+               // Output on terminal
+               {
+                       name:               "testcase2",
+                       expectedResult:     true,
+                       isTerminal:         true,
+                       disableColor:       false,
+                       forceColor:         false,
+                       envColor:           false,
+                       clicolorIsSet:      false,
+                       clicolorForceIsSet: false,
+               },
+               // Output on terminal with color disabled
+               {
+                       name:               "testcase3",
+                       expectedResult:     false,
+                       isTerminal:         true,
+                       disableColor:       true,
+                       forceColor:         false,
+                       envColor:           false,
+                       clicolorIsSet:      false,
+                       clicolorForceIsSet: false,
+               },
+               // Output not on terminal with color disabled
+               {
+                       name:               "testcase4",
+                       expectedResult:     false,
+                       isTerminal:         false,
+                       disableColor:       true,
+                       forceColor:         false,
+                       envColor:           false,
+                       clicolorIsSet:      false,
+                       clicolorForceIsSet: false,
+               },
+               // Output not on terminal with color forced
+               {
+                       name:               "testcase5",
+                       expectedResult:     true,
+                       isTerminal:         false,
+                       disableColor:       false,
+                       forceColor:         true,
+                       envColor:           false,
+                       clicolorIsSet:      false,
+                       clicolorForceIsSet: false,
+               },
+               // Output on terminal with clicolor set to "0"
+               {
+                       name:               "testcase6",
+                       expectedResult:     false,
+                       isTerminal:         true,
+                       disableColor:       false,
+                       forceColor:         false,
+                       envColor:           true,
+                       clicolorIsSet:      true,
+                       clicolorForceIsSet: false,
+                       clicolorVal:        "0",
+               },
+               // Output on terminal with clicolor set to "1"
+               {
+                       name:               "testcase7",
+                       expectedResult:     true,
+                       isTerminal:         true,
+                       disableColor:       false,
+                       forceColor:         false,
+                       envColor:           true,
+                       clicolorIsSet:      true,
+                       clicolorForceIsSet: false,
+                       clicolorVal:        "1",
+               },
+               // Output not on terminal with clicolor set to "0"
+               {
+                       name:               "testcase8",
+                       expectedResult:     false,
+                       isTerminal:         false,
+                       disableColor:       false,
+                       forceColor:         false,
+                       envColor:           true,
+                       clicolorIsSet:      true,
+                       clicolorForceIsSet: false,
+                       clicolorVal:        "0",
+               },
+               // Output not on terminal with clicolor set to "1"
+               {
+                       name:               "testcase9",
+                       expectedResult:     false,
+                       isTerminal:         false,
+                       disableColor:       false,
+                       forceColor:         false,
+                       envColor:           true,
+                       clicolorIsSet:      true,
+                       clicolorForceIsSet: false,
+                       clicolorVal:        "1",
+               },
+               // Output not on terminal with clicolor set to "1" and force color
+               {
+                       name:               "testcase10",
+                       expectedResult:     true,
+                       isTerminal:         false,
+                       disableColor:       false,
+                       forceColor:         true,
+                       envColor:           true,
+                       clicolorIsSet:      true,
+                       clicolorForceIsSet: false,
+                       clicolorVal:        "1",
+               },
+               // Output not on terminal with clicolor set to "0" and force color
+               {
+                       name:               "testcase11",
+                       expectedResult:     false,
+                       isTerminal:         false,
+                       disableColor:       false,
+                       forceColor:         true,
+                       envColor:           true,
+                       clicolorIsSet:      true,
+                       clicolorForceIsSet: false,
+                       clicolorVal:        "0",
+               },
+               // Output not on terminal with clicolor_force set to "1"
+               {
+                       name:               "testcase12",
+                       expectedResult:     true,
+                       isTerminal:         false,
+                       disableColor:       false,
+                       forceColor:         false,
+                       envColor:           true,
+                       clicolorIsSet:      false,
+                       clicolorForceIsSet: true,
+                       clicolorForceVal:   "1",
+               },
+               // Output not on terminal with clicolor_force set to "0"
+               {
+                       name:               "testcase13",
+                       expectedResult:     false,
+                       isTerminal:         false,
+                       disableColor:       false,
+                       forceColor:         false,
+                       envColor:           true,
+                       clicolorIsSet:      false,
+                       clicolorForceIsSet: true,
+                       clicolorForceVal:   "0",
+               },
+               // Output on terminal with clicolor_force set to "0"
+               {
+                       name:               "testcase14",
+                       expectedResult:     false,
+                       isTerminal:         true,
+                       disableColor:       false,
+                       forceColor:         false,
+                       envColor:           true,
+                       clicolorIsSet:      false,
+                       clicolorForceIsSet: true,
+                       clicolorForceVal:   "0",
+               },
+       }
+
+       cleanenv := func() {
+               os.Unsetenv("CLICOLOR")
+               os.Unsetenv("CLICOLOR_FORCE")
+       }
+
+       defer cleanenv()
+
+       for _, val := range params {
+               t.Run("textformatter_"+val.name, func(subT *testing.T) {
+                       tf := TextFormatter{
+                               isTerminal:                val.isTerminal,
+                               DisableColors:             val.disableColor,
+                               ForceColors:               val.forceColor,
+                               EnvironmentOverrideColors: val.envColor,
+                       }
+                       cleanenv()
+                       if val.clicolorIsSet {
+                               os.Setenv("CLICOLOR", val.clicolorVal)
+                       }
+                       if val.clicolorForceIsSet {
+                               os.Setenv("CLICOLOR_FORCE", val.clicolorForceVal)
+                       }
+                       res := tf.isColored()
+                       assert.Equal(subT, val.expectedResult, res)
+               })
+       }
+}
+
+func TestCustomSorting(t *testing.T) {
+       formatter := &TextFormatter{
+               DisableColors: true,
+               SortingFunc: func(keys []string) {
+                       sort.Slice(keys, func(i, j int) bool {
+                               if keys[j] == "prefix" {
+                                       return false
+                               }
+                               if keys[i] == "prefix" {
+                                       return true
+                               }
+                               return strings.Compare(keys[i], keys[j]) == -1
+                       })
+               },
+       }
+
+       entry := &Entry{
+               Message: "Testing custom sort function",
+               Time:    time.Now(),
+               Level:   InfoLevel,
+               Data: Fields{
+                       "test":      "testvalue",
+                       "prefix":    "the application prefix",
+                       "blablabla": "blablabla",
+               },
+       }
+       b, err := formatter.Format(entry)
+       require.NoError(t, err)
+       require.True(t, strings.HasPrefix(string(b), "prefix="), "format output is %q", string(b))
+}
index 37dfd94..d9b9825 100644 (file)
@@ -4,7 +4,6 @@ os:
         - linux
 
 go:
-        - 1.7
         - 1.8
         - 1.9
         - tip
index b8e4f31..8b2214c 100644 (file)
@@ -66,4 +66,7 @@ type Client interface {
 
        // Disconnected returns a receiving channel, which is closed when the client disconnects.
        Disconnected() <-chan struct{}
+
+       // Conn returns the client's current connection.
+       Conn() Conn
 }
index 4934a60..4309789 100644 (file)
@@ -176,6 +176,7 @@ func (cln *Client) ReadCharacteristic(c *ble.Characteristic) ([]byte, error) {
        if rsp.err() != nil {
                return nil, rsp.err()
        }
+       c.Value = rsp.data()
        return rsp.data(), nil
 }
 
@@ -215,6 +216,7 @@ func (cln *Client) ReadDescriptor(d *ble.Descriptor) ([]byte, error) {
        if err := rsp.err(); err != nil {
                return nil, err
        }
+       d.Value = rsp.data()
        return rsp.data(), nil
 }
 
@@ -318,6 +320,11 @@ func (cln *Client) Disconnected() <-chan struct{} {
        return cln.conn.Disconnected()
 }
 
+// Conn returns the client's current connection.
+func (cln *Client) Conn() ble.Conn {
+       return cln.conn
+}
+
 type sub struct {
        fn   ble.NotificationHandler
        char *ble.Characteristic
index 9607981..597f186 100644 (file)
@@ -9,10 +9,10 @@ import (
        "github.com/raff/goble/xpc"
 )
 
-func newConn(d *Device, a ble.Addr) *conn {
+func newConn(d *Device, a ble.Addr, rxMTU int) *conn {
        return &conn{
                dev:   d,
-               rxMTU: 23,
+               rxMTU: rxMTU,
                txMTU: 23,
                addr:  a,
                done:  make(chan struct{}),
@@ -43,6 +43,8 @@ type conn struct {
        notifiers map[uint16]ble.Notifier // central connection only
 
        subs map[uint16]*sub
+
+       isConnected bool
 }
 
 func (c *conn) Context() context.Context {
@@ -75,7 +77,9 @@ func (c *conn) TxMTU() int {
 }
 
 func (c *conn) SetTxMTU(mtu int) {
+       c.Lock()
        c.txMTU = mtu
+       c.Unlock()
 }
 
 func (c *conn) Read(b []byte) (int, error) {
index c94abc6..f6e941a 100644 (file)
@@ -37,7 +37,7 @@ type Device struct {
 }
 
 // NewDevice returns a BLE device.
-func NewDevice(opts ...Option) (*Device, error) {
+func NewDevice(opts ...ble.Option) (*Device, error) {
        err := initXpcIDs()
        if err != nil {
                return nil, err
@@ -61,7 +61,7 @@ func NewDevice(opts ...Option) (*Device, error) {
 }
 
 // Option sets the options specified.
-func (d *Device) Option(opts ...Option) error {
+func (d *Device) Option(opts ...ble.Option) error {
        var err error
        for _, opt := range opts {
                err = opt(d)
@@ -489,10 +489,15 @@ func (d *Device) HandleXpcEvent(event xpc.Dict, err error) {
                d.conn(args).unsubscribed(d.chars[args.attributeID()])
 
        case evtPeripheralConnected:
-               d.chConn <- d.conn(args)
+               c := d.conn(args)
+               if !c.isConnected {
+                       c.isConnected = true
+                       d.chConn <- c
+               }
 
        case evtPeripheralDisconnected:
                c := d.conn(args)
+               c.isConnected = false
                select {
                case c.rspc <- m:
                        // Canceled by local central synchronously
@@ -541,7 +546,7 @@ func (d *Device) conn(m msg) *conn {
        d.connLock.Lock()
        c, ok := d.conns[a.String()]
        if !ok {
-               c = newConn(d, a)
+               c = newConn(d, a, m.attMTU())
                d.conns[a.String()] = c
        }
        d.connLock.Unlock()
index 9360b51..87b3c5a 100644 (file)
@@ -12,7 +12,12 @@ func (m msg) args() xpc.Dict { return xpc.Dict(m).MustGetDict("kCBMsgArgs") }
 func (m msg) advertisementData() xpc.Dict {
        return xpc.Dict(m).MustGetDict("kCBMsgArgAdvertisementData")
 }
-func (m msg) attMTU() int          { return xpc.Dict(m).MustGetInt("kCBMsgArgATTMTU") }
+
+const macOSXDefaultMTU = 23
+
+// Uses GetInt as oppose to MustGetInt due to OSX not supporting 'kCBMsgArgATTMTU'.
+// Issue #29
+func (m msg) attMTU() int          { return xpc.Dict(m).GetInt("kCBMsgArgATTMTU", macOSXDefaultMTU) }
 func (m msg) attWrites() xpc.Array { return xpc.Dict(m).MustGetArray("kCBMsgArgATTWrites") }
 func (m msg) attributeID() int     { return xpc.Dict(m).MustGetInt("kCBMsgArgAttributeID") }
 func (m msg) characteristicHandle() int {
index 924d1d5..cdbdde3 100644 (file)
@@ -1,20 +1,40 @@
 package darwin
 
-// An Option is a configuration function, which configures the device.
-type Option func(*Device) error
-
-// OptPeripheralRole configures the device to perform Peripheral tasks.
-func OptPeripheralRole() Option {
-       return func(d *Device) error {
-               d.role = 1
-               return nil
-       }
+import (
+       "errors"
+       "time"
+
+       "github.com/go-ble/ble/linux/hci/cmd"
+)
+
+// SetPeripheralRole configures the device to perform Peripheral tasks.
+func (d *Device) SetPeripheralRole() error {
+       d.role = 1
+       return nil
+}
+
+// SetCentralRole configures the device to perform Central tasks.
+func (d *Device) SetCentralRole() error {
+       d.role = 0
+       return nil
+}
+
+// SetDeviceID sets HCI device ID.
+func (d *Device) SetDeviceID(id int) error {
+       return errors.New("Not supported")
+}
+
+// SetDialerTimeout sets dialing timeout for Dialer.
+func (d *Device) SetDialerTimeout(dur time.Duration) error {
+       return errors.New("Not supported")
+}
+
+// SetListenerTimeout sets dialing timeout for Listener.
+func (d *Device) SetListenerTimeout(dur time.Duration) error {
+       return errors.New("Not supported")
 }
 
-// OptCentralRole configures the device to perform Central tasks.
-func OptCentralRole() Option {
-       return func(d *Device) error {
-               d.role = 0
-               return nil
-       }
+// SetConnParams overrides default connection parameters.
+func (d *Device) SetConnParams(param cmd.LECreateConnection) error {
+       return errors.New("Not supported")
 }
index 9752ea1..e1d2cb0 100644 (file)
@@ -1,8 +1,8 @@
 package main
 
 import (
+       "github.com/go-ble/ble"
        "github.com/go-ble/ble/linux"
-       "github.com/go-ble/ble/linux/hci"
        "github.com/go-ble/ble/linux/hci/cmd"
        "github.com/pkg/errors"
 )
@@ -31,7 +31,7 @@ func updateLinuxParam(d *linux.Device) error {
                return errors.Wrap(err, "can't set scan param")
        }
 
-       if err := d.HCI.Option(hci.OptConnParams(
+       if err := d.HCI.Option(ble.OptConnParams(
                cmd.LECreateConnection{
                        LEScanInterval:        0x0004,    // 0x0004 - 0x4000; N * 0.625 msec
                        LEScanWindow:          0x0004,    // 0x0004 - 0x4000; N * 0.625 msec
index 3ca967f..dd46f76 100644 (file)
@@ -6,6 +6,6 @@ import (
 )
 
 // DefaultDevice ...
-func DefaultDevice() (d ble.Device, err error) {
-       return darwin.NewDevice()
+func DefaultDevice(opts ...ble.Option) (d ble.Device, err error) {
+       return darwin.NewDevice(opts...)
 }
index c01e9bd..eca7bf6 100644 (file)
@@ -6,6 +6,6 @@ import (
 )
 
 // DefaultDevice ...
-func DefaultDevice() (d ble.Device, err error) {
-       return linux.NewDevice()
+func DefaultDevice(opts ...ble.Option) (d ble.Device, err error) {
+       return linux.NewDevice(opts...)
 }
index e449a6a..cd197e1 100644 (file)
@@ -1,8 +1,10 @@
 package dev
 
-import "github.com/go-ble/ble"
+import (
+       "github.com/go-ble/ble"
+)
 
 // NewDevice ...
-func NewDevice(impl string) (d ble.Device, err error) {
-       return DefaultDevice()
+func NewDevice(impl string, opts ...ble.Option) (d ble.Device, err error) {
+       return DefaultDevice(opts...)
 }
index 6ffc753..f15e615 100644 (file)
@@ -185,6 +185,47 @@ func (p *Packet) Field(typ byte) []byte {
        return nil
 }
 
+func (p *Packet) getUUIDsByType(typ byte, u []ble.UUID, w int) []ble.UUID {
+       pos := 0
+       var b []byte
+       for pos < len(p.b) {
+               if b, pos = p.fieldPos(typ, pos); b != nil {
+                       u = uuidList(u, b, w)
+               }
+       }
+       return u
+}
+
+func (p *Packet) fieldPos(typ byte, offset int) ([]byte, int) {
+       if offset >= len(p.b) {
+               return nil, len(p.b)
+       }
+
+       b := p.b[offset:]
+       pos := offset
+
+       if len(b) < 2 {
+               return nil, pos + len(b)
+       }
+
+       for len(b) > 0 {
+               l, t := b[0], b[1]
+               if int(l) < 1 || len(b) < int(1+l) {
+                       return nil, pos
+               }
+               if t == typ {
+                       r := b[2 : 2+l-1]
+                       return r, pos + 1 + int(l)
+               }
+               b = b[1+l:]
+               pos += 1 + int(l)
+               if len(b) < 2 {
+                       break
+               }
+       }
+       return nil, pos
+}
+
 // Flags returns the flags of the packet.
 func (p *Packet) Flags() (flags byte, present bool) {
        b := p.Field(flags)
@@ -214,24 +255,12 @@ func (p *Packet) TxPower() (power int, present bool) {
 // UUIDs returns a list of service UUIDs.
 func (p *Packet) UUIDs() []ble.UUID {
        var u []ble.UUID
-       if b := p.Field(someUUID16); b != nil {
-               u = uuidList(u, b, 2)
-       }
-       if b := p.Field(allUUID16); b != nil {
-               u = uuidList(u, b, 2)
-       }
-       if b := p.Field(someUUID32); b != nil {
-               u = uuidList(u, b, 4)
-       }
-       if b := p.Field(allUUID32); b != nil {
-               u = uuidList(u, b, 4)
-       }
-       if b := p.Field(someUUID128); b != nil {
-               u = uuidList(u, b, 16)
-       }
-       if b := p.Field(allUUID128); b != nil {
-               u = uuidList(u, b, 16)
-       }
+       u = p.getUUIDsByType(someUUID16, u, 2)
+       u = p.getUUIDsByType(allUUID16, u, 2)
+       u = p.getUUIDsByType(someUUID32, u, 4)
+       u = p.getUUIDsByType(allUUID32, u, 4)
+       u = p.getUUIDsByType(someUUID128, u, 16)
+       u = p.getUUIDsByType(allUUID128, u, 16)
        return u
 }
 
index 93585a2..bee9750 100644 (file)
@@ -25,7 +25,7 @@ func (r *DB) idx(h int) int {
        if h < int(r.base) {
                return tooSmall
        }
-       if int(h) >= int(r.base)+len(r.attrs) {
+       if h >= int(r.base)+len(r.attrs) {
                return tooLarge
        }
        return h - int(r.base)
index 5056ebc..0ff0703 100644 (file)
@@ -302,7 +302,7 @@ func (s *Server) handleFindByTypeValueRequest(r FindByTypeValueRequest) []byte {
 
        for _, a := range s.db.subrange(r.StartingHandle(), r.EndingHandle()) {
                v, starth, endh := a.v, a.h, a.endh
-               if !(ble.UUID(a.typ).Equal(ble.UUID16(r.AttributeType()))) {
+               if !a.typ.Equal(ble.UUID16(r.AttributeType())) {
                        continue
                }
                if v == nil {
index 521eb9f..86fe565 100644 (file)
@@ -13,17 +13,17 @@ import (
 )
 
 // NewDevice returns the default HCI device.
-func NewDevice() (*Device, error) {
-       return NewDeviceWithName("Gopher")
+func NewDevice(opts ...ble.Option) (*Device, error) {
+       return NewDeviceWithName("Gopher", opts...)
 }
 
 // NewDeviceWithName returns the default HCI device.
-func NewDeviceWithName(name string) (*Device, error) {
-       return NewDeviceWithNameAndHandler(name, nil)
+func NewDeviceWithName(name string, opts ...ble.Option) (*Device, error) {
+       return NewDeviceWithNameAndHandler(name, nil, opts...)
 }
 
-func NewDeviceWithNameAndHandler(name string, handler ble.NotifyHandler) (*Device, error) {
-       dev, err := hci.NewHCI()
+func NewDeviceWithNameAndHandler(name string, handler ble.NotifyHandler, opts ...ble.Option) (*Device, error) {
+       dev, err := hci.NewHCI(opts...)
        if err != nil {
                return nil, errors.Wrap(err, "can't create hci")
        }
index 3ad108e..e138d9e 100644 (file)
@@ -208,7 +208,13 @@ func (p *Client) DiscoverDescriptors(filter []ble.UUID, c *ble.Characteristic) (
 func (p *Client) ReadCharacteristic(c *ble.Characteristic) ([]byte, error) {
        p.Lock()
        defer p.Unlock()
-       return p.ac.Read(c.ValueHandle)
+       val, err := p.ac.Read(c.ValueHandle)
+       if err != nil {
+               return nil, err
+       }
+
+       c.Value = val
+       return val, nil
 }
 
 // ReadLongCharacteristic reads a characteristic value which is longer than the MTU. [Vol 3, Part G, 4.8.3]
@@ -231,6 +237,8 @@ func (p *Client) ReadLongCharacteristic(c *ble.Characteristic) ([]byte, error) {
                }
                buffer = append(buffer, read...)
        }
+
+       c.Value = buffer
        return buffer, nil
 }
 
@@ -248,7 +256,13 @@ func (p *Client) WriteCharacteristic(c *ble.Characteristic, v []byte, noRsp bool
 func (p *Client) ReadDescriptor(d *ble.Descriptor) ([]byte, error) {
        p.Lock()
        defer p.Unlock()
-       return p.ac.Read(d.Handle)
+       val, err := p.ac.Read(d.Handle)
+       if err != nil {
+               return nil, err
+       }
+
+       d.Value = val
+       return val, nil
 }
 
 // WriteDescriptor writes a characteristic descriptor to a server. [Vol 3, Part G, 4.12.3]
@@ -357,6 +371,11 @@ func (p *Client) Disconnected() <-chan struct{} {
        return p.conn.Disconnected()
 }
 
+// Conn returns the client's current connection.
+func (p *Client) Conn() ble.Conn {
+       return p.conn
+}
+
 // HandleNotification ...
 func (p *Client) HandleNotification(req []byte) {
        p.Lock()
index 46a46f8..d6c39fc 100644 (file)
@@ -33,9 +33,6 @@ type Conn struct {
        txMTU int
        rxMPS int
 
-       // leFrame is set to be true when the LE Credit based flow control is used.
-       leFrame bool
-
        // Signaling MTUs are The maximum size of command information that the
        // L2CAP layer entity is capable of accepting.
        // A L2CAP implementations supporting LE-U should support at least 23 bytes.
@@ -47,23 +44,25 @@ type Conn struct {
        sigRxMTU int
        sigTxMTU int
 
-       // sigID is used to match responses with signaling requests.
-       // The requesting device sets this field and the responding device uses the
-       // same value in its response. Within each signalling channel a different
-       // Identifier shall be used for each successive command. [Vol 3, Part A, 4]
-       sigID uint8
-
        sigSent chan []byte
-       smpSent chan []byte
+       // smpSent chan []byte
 
        chInPkt chan packet
        chInPDU chan pdu
 
+       chDone chan struct{}
        // Host to Controller Data Flow Control pkt-based Data flow control for LE-U [Vol 2, Part E, 4.1.1]
        // chSentBufs tracks the HCI buffer occupied by this connection.
        txBuffer *Client
 
-       chDone chan struct{}
+       // sigID is used to match responses with signaling requests.
+       // The requesting device sets this field and the responding device uses the
+       // same value in its response. Within each signalling channel a different
+       // Identifier shall be used for each successive command. [Vol 3, Part A, 4]
+       sigID uint8
+
+       // leFrame is set to be true when the LE Credit based flow control is used.
+       leFrame bool
 }
 
 func newConn(h *HCI, param evt.LEConnectionComplete) *Conn {
@@ -94,7 +93,7 @@ func newConn(h *HCI, param evt.LEConnectionComplete) *Conn {
                                if err != io.EOF {
                                        // TODO: wrap and pass the error up.
                                        // err := errors.Wrap(err, "recombine failed")
-                                       logger.Error("recombine failed: ", "err", err)
+                                       _ = logger.Error("recombine failed: ", "err", err)
                                }
                                close(c.chInPDU)
                                return
@@ -140,7 +139,7 @@ func (c *Conn) Read(sdu []byte) (n int, err error) {
        buf.Write(data)
        for buf.Len() < slen {
                p := <-c.chInPDU
-               buf.Write(pdu(p).payload())
+               buf.Write(p.payload())
        }
        return slen, nil
 }
@@ -205,10 +204,23 @@ func (c *Conn) writePDU(pdu []byte) (int, error) {
                }
 
                // Prepare the Headers
-               binary.Write(pkt, binary.LittleEndian, uint8(pktTypeACLData))                         // HCI Header: pkt Type
-               binary.Write(pkt, binary.LittleEndian, uint16(c.param.ConnectionHandle()|(flags<<8))) // ACL Header: handle and flags
-               binary.Write(pkt, binary.LittleEndian, uint16(flen))                                  // ACL Header: data len
-               binary.Write(pkt, binary.LittleEndian, pdu[:flen])                                    // Append payload
+
+               // HCI Header: pkt Type
+               if err := binary.Write(pkt, binary.LittleEndian, pktTypeACLData); err != nil {
+                       return 0, err
+               }
+               // ACL Header: handle and flags
+               if err := binary.Write(pkt, binary.LittleEndian, c.param.ConnectionHandle()|(flags<<8)); err != nil {
+                       return 0, err
+               }
+               // ACL Header: data len
+               if err := binary.Write(pkt, binary.LittleEndian, uint16(flen)); err != nil {
+                       return 0, err
+               }
+               // Append payload
+               if err := binary.Write(pkt, binary.LittleEndian, pdu[:flen]); err != nil {
+                       return 0, err
+               }
 
                // Flush the pkt to HCI
                select {
index d114a0a..6bb1f1e 100644 (file)
@@ -209,28 +209,35 @@ func (h *HCI) Dial(ctx context.Context, a ble.Addr) (ble.Client, error) {
        if h.dialerTmo != time.Duration(0) {
                tmo = time.After(h.dialerTmo)
        }
+
        select {
        case <-ctx.Done():
-               return nil, ctx.Err()
+               return h.cancelDial()
+       case <-tmo:
+               return h.cancelDial()
        case <-h.done:
                return nil, h.err
        case c := <-h.chMasterConn:
                return gatt.NewClient(c)
-       case <-tmo:
-               err := h.Send(&h.params.connCancel, nil)
-               if err == nil {
-                       // The pending connection was canceled successfully.
-                       return nil, fmt.Errorf("connection timed out")
-               }
-               // The connection has been established, the cancel command
-               // failed with ErrDisallowed.
-               if err == ErrDisallowed {
-                       return gatt.NewClient(<-h.chMasterConn)
-               }
-               return nil, errors.Wrap(err, "cancel connection failed")
+
        }
 }
 
+// cancelDial cancels the Dialing
+func (h *HCI) cancelDial() (ble.Client, error) {
+       err := h.Send(&h.params.connCancel, nil)
+       if err == nil {
+               // The pending connection was canceled successfully.
+               return nil, fmt.Errorf("connection canceled")
+       }
+       // The connection has been established, the cancel command
+       // failed with ErrDisallowed.
+       if err == ErrDisallowed {
+               return gatt.NewClient(<-h.chMasterConn)
+       }
+       return nil, errors.Wrap(err, "cancel connection failed")
+}
+
 // Advertise starts advertising.
 func (h *HCI) Advertise() error {
        h.params.advEnable.AdvertisingEnable = 1
index 3e5b9fd..4abd849 100644 (file)
@@ -4,6 +4,7 @@ import (
        "fmt"
        "io"
        "net"
+       "strings"
        "sync"
        "time"
 
@@ -34,13 +35,14 @@ type pkt struct {
 }
 
 // NewHCI returns a hci device.
-func NewHCI(opts ...Option) (*HCI, error) {
+func NewHCI(opts ...ble.Option) (*HCI, error) {
        h := &HCI{
                id: -1,
 
                chCmdPkt:  make(chan *pkt),
-               chCmdBufs: make(chan []byte, 8),
+               chCmdBufs: make(chan []byte, 16),
                sent:      make(map[int]*pkt),
+               muSent:    &sync.Mutex{},
 
                evth: map[int]handlerFn{},
                subh: map[int]handlerFn{},
@@ -72,6 +74,7 @@ type HCI struct {
        // Host to Controller command flow control [Vol 2, Part E, 4.4]
        chCmdPkt  chan *pkt
        chCmdBufs chan []byte
+       muSent    *sync.Mutex
        sent      map[int]*pkt
 
        // evtHub
@@ -168,7 +171,7 @@ func (h *HCI) Error() error {
 }
 
 // Option sets the options specified.
-func (h *HCI) Option(opts ...Option) error {
+func (h *HCI) Option(opts ...ble.Option) error {
        var err error
        for _, opt := range opts {
                err = opt(h)
@@ -247,7 +250,9 @@ func (h *HCI) send(c Command) ([]byte, error) {
                h.close(fmt.Errorf("hci: failed to marshal cmd"))
        }
 
-       h.sent[c.OpCode()] = p // TODO: lock
+       h.muSent.Lock()
+       h.sent[c.OpCode()] = p
+       h.muSent.Unlock()
        if n, err := h.skt.Write(b[:4+c.Len()]); err != nil {
                h.close(fmt.Errorf("hci: failed to send cmd"))
        } else if n != 4+c.Len() {
@@ -274,8 +279,14 @@ func (h *HCI) sktLoop() {
                p := make([]byte, n)
                copy(p, b)
                if err := h.handlePkt(p); err != nil {
-                       h.err = fmt.Errorf("skt: %s", err)
-                       return
+                       // Some bluetooth devices may append vendor specific packets at the last,
+                       // in this case, simply ignore them.
+                       if strings.HasPrefix(err.Error(), "unsupported vendor packet:") {
+                               _ = logger.Error("skt: %v", err)
+                       } else {
+                               h.err = fmt.Errorf("skt: %v", err)
+                               return
+                       }
                }
        }
 }
@@ -310,7 +321,7 @@ func (h *HCI) handleACL(b []byte) error {
        c, ok := h.conns[handle]
        h.muConns.Unlock()
        if !ok {
-               logger.Warn("invalid connection handle on ACL packet", "handle", handle)
+               _ = logger.Warn("invalid connection handle on ACL packet", "handle", handle)
                return nil
        }
        c.chInPkt <- b
@@ -402,10 +413,12 @@ func (h *HCI) handleCommandComplete(b []byte) error {
 
        // NOP command, used for flow control purpose [Vol 2, Part E, 4.4]
        if e.CommandOpcode() == 0x0000 {
-               h.chCmdBufs = make(chan []byte, 8)
+               h.chCmdBufs = make(chan []byte, 16)
                return nil
        }
+       h.muSent.Lock()
        p, found := h.sent[int(e.CommandOpcode())]
+       h.muSent.Unlock()
        if !found {
                return fmt.Errorf("can't find the cmd for CommandCompleteEP: % X", e)
        }
@@ -419,7 +432,9 @@ func (h *HCI) handleCommandStatus(b []byte) error {
                h.chCmdBufs <- make([]byte, 64)
        }
 
+       h.muSent.Lock()
        p, found := h.sent[int(e.CommandOpcode())]
+       h.muSent.Unlock()
        if !found {
                return fmt.Errorf("can't find the cmd for CommandStatusEP: % X", e)
        }
@@ -435,7 +450,11 @@ func (h *HCI) handleLEConnectionComplete(b []byte) error {
        h.muConns.Unlock()
        if e.Role() == roleMaster {
                if e.Status() == 0x00 {
-                       h.chMasterConn <- c
+                       select {
+                       case h.chMasterConn <- c:
+                       default:
+                               go c.Close()
+                       }
                        return nil
                }
                if ErrCommand(e.Status()) == ErrConnID {
@@ -479,7 +498,7 @@ func (h *HCI) handleDisconnectionComplete(b []byte) error {
        }
        close(c.chInPkt)
 
-       if c.param.Role() == roleMaster {
+       if c.param.Role() == roleSlave {
                // Re-enable advertising, if it was advertising. Refer to the
                // handleLEConnectionComplete() for details.
                // This may failed with ErrCommandDisallowed, if the controller
index b0f4832..d765d80 100644 (file)
@@ -1,42 +1,42 @@
 package hci
 
 import (
+       "errors"
        "time"
 
        "github.com/go-ble/ble/linux/hci/cmd"
 )
 
-// An Option is a configuration function, which configures the device.
-type Option func(*HCI) error
+// SetDeviceID sets HCI device ID.
+func (h *HCI) SetDeviceID(id int) error {
+       h.id = id
+       return nil
+}
+
+// SetDialerTimeout sets dialing timeout for Dialer.
+func (h *HCI) SetDialerTimeout(d time.Duration) error {
+       h.dialerTmo = d
+       return nil
+}
 
-// OptDeviceID sets HCI device ID.
-func OptDeviceID(id int) Option {
-       return func(h *HCI) error {
-               h.id = id
-               return nil
-       }
+// SetListenerTimeout sets dialing timeout for Listener.
+func (h *HCI) SetListenerTimeout(d time.Duration) error {
+       h.listenerTmo = d
+       return nil
 }
 
-// OptDialerTimeout sets dialing timeout for Dialer.
-func OptDialerTimeout(d time.Duration) Option {
-       return func(h *HCI) error {
-               h.dialerTmo = d
-               return nil
-       }
+// SetConnParams overrides default connection parameters.
+func (h *HCI) SetConnParams(param cmd.LECreateConnection) error {
+       h.params.connParams = param
+       return nil
 }
 
-// OptListenerTimeout sets dialing timeout for Listener.
-func OptListenerTimeout(d time.Duration) Option {
-       return func(h *HCI) error {
-               h.listenerTmo = d
-               return nil
-       }
+// SetPeripheralRole is not supported
+func (h *HCI) SetPeripheralRole() error {
+       return errors.New("Not supported")
 }
 
-// OptConnParams overrides default connection parameters.
-func OptConnParams(param cmd.LECreateConnection) Option {
-       return func(h *HCI) error {
-               h.params.connParams = param
-               return nil
-       }
+// SetCentralRole is not supported
+func (h *HCI) SetCentralRole() error {
+       return errors.New("Not supported")
 }
index 0badb58..9a18745 100644 (file)
@@ -13,7 +13,7 @@ import (
 // Signal ...
 type Signal interface {
        Code() int
-       Marshal() []byte
+       Marshal() ([]byte, error)
        Unmarshal([]byte) error
 }
 
@@ -26,15 +26,30 @@ func (s sigCmd) data() []byte { return s[4 : 4+s.len()] }
 
 // Signal ...
 func (c *Conn) Signal(req Signal, rsp Signal) error {
-       data := req.Marshal()
+       data, err := req.Marshal()
+       if err != nil {
+               return err
+       }
        buf := bytes.NewBuffer(make([]byte, 0))
-       binary.Write(buf, binary.LittleEndian, uint16(4+len(data)))
-       binary.Write(buf, binary.LittleEndian, uint16(cidLESignal))
+       if err := binary.Write(buf, binary.LittleEndian, uint16(4+len(data))); err != nil {
+               return err
+       }
+       if err := binary.Write(buf, binary.LittleEndian, cidLESignal); err != nil {
+               return err
+       }
 
-       binary.Write(buf, binary.LittleEndian, uint8(req.Code()))
-       binary.Write(buf, binary.LittleEndian, uint8(c.sigID))
-       binary.Write(buf, binary.LittleEndian, uint16(len(data)))
-       binary.Write(buf, binary.LittleEndian, data)
+       if err := binary.Write(buf, binary.LittleEndian, uint8(req.Code())); err != nil {
+               return err
+       }
+       if err := binary.Write(buf, binary.LittleEndian, uint8(c.sigID)); err != nil {
+               return err
+       }
+       if err := binary.Write(buf, binary.LittleEndian, uint16(len(data))); err != nil {
+               return err
+       }
+       if err := binary.Write(buf, binary.LittleEndian, data); err != nil {
+               return err
+       }
 
        c.sigSent = make(chan []byte)
        defer close(c.sigSent)
@@ -63,13 +78,26 @@ func (c *Conn) Signal(req Signal, rsp Signal) error {
 }
 
 func (c *Conn) sendResponse(code uint8, id uint8, r Signal) (int, error) {
-       data := r.Marshal()
+       data, err := r.Marshal()
+       if err != nil {
+               return 0, err
+       }
        buf := bytes.NewBuffer(make([]byte, 0))
-       binary.Write(buf, binary.LittleEndian, uint16(4+len(data)))
-       binary.Write(buf, binary.LittleEndian, uint16(cidLESignal))
-       binary.Write(buf, binary.LittleEndian, uint8(code))
-       binary.Write(buf, binary.LittleEndian, uint8(id))
-       binary.Write(buf, binary.LittleEndian, uint16(len(data)))
+       if err := binary.Write(buf, binary.LittleEndian, uint16(4+len(data))); err != nil {
+               return 0, err
+       }
+       if err := binary.Write(buf, binary.LittleEndian, cidLESignal); err != nil {
+               return 0, err
+       }
+       if err := binary.Write(buf, binary.LittleEndian, code); err != nil {
+               return 0, err
+       }
+       if err := binary.Write(buf, binary.LittleEndian, id); err != nil {
+               return 0, err
+       }
+       if err := binary.Write(buf, binary.LittleEndian, uint16(len(data))); err != nil {
+               return 0, err
+       }
        if err := binary.Write(buf, binary.LittleEndian, data); err != nil {
                return 0, err
        }
@@ -85,13 +113,16 @@ func (c *Conn) handleSignal(p pdu) error {
        // command in the L2CAP packet. If only Responses are recognized, the packet
        // shall be silently discarded. [Vol3, Part A, 4.1]
        if p.dlen() > c.sigRxMTU {
-               c.sendResponse(
+               _, err := c.sendResponse(
                        SignalCommandReject,
                        sigCmd(p.payload()).id(),
                        &CommandReject{
                                Reason: 0x0001,                                            // Signaling MTU exceeded.
                                Data:   []byte{uint8(c.sigRxMTU), uint8(c.sigRxMTU >> 8)}, // Actual MTUsig.
                        })
+               if err != nil {
+                       _ = logger.Error("send repsonse", fmt.Sprintf("%v", err))
+               }
                return nil
        }
 
index 6f2d988..ed1d828 100644 (file)
@@ -18,10 +18,12 @@ type CommandReject struct {
 func (s CommandReject) Code() int { return 0x01 }
 
 // Marshal serializes the command parameters into binary form.
-func (s *CommandReject) Marshal() []byte {
+func (s *CommandReject) Marshal() ([]byte, error) {
        buf := bytes.NewBuffer(make([]byte, 0))
-       binary.Write(buf, binary.LittleEndian, s)
-       return buf.Bytes()
+       if err := binary.Write(buf, binary.LittleEndian, s); err != nil {
+               return nil, err
+       }
+       return buf.Bytes(), nil
 }
 
 // Unmarshal de-serializes the binary data and stores the result in the receiver.
@@ -42,10 +44,12 @@ type DisconnectRequest struct {
 func (s DisconnectRequest) Code() int { return 0x06 }
 
 // Marshal serializes the command parameters into binary form.
-func (s *DisconnectRequest) Marshal() []byte {
+func (s *DisconnectRequest) Marshal() ([]byte, error) {
        buf := bytes.NewBuffer(make([]byte, 0))
-       binary.Write(buf, binary.LittleEndian, s)
-       return buf.Bytes()
+       if err := binary.Write(buf, binary.LittleEndian, s); err != nil {
+               return nil, err
+       }
+       return buf.Bytes(), nil
 }
 
 // Unmarshal de-serializes the binary data and stores the result in the receiver.
@@ -66,10 +70,12 @@ type DisconnectResponse struct {
 func (s DisconnectResponse) Code() int { return 0x07 }
 
 // Marshal serializes the command parameters into binary form.
-func (s *DisconnectResponse) Marshal() []byte {
+func (s *DisconnectResponse) Marshal() ([]byte, error) {
        buf := bytes.NewBuffer(make([]byte, 0))
-       binary.Write(buf, binary.LittleEndian, s)
-       return buf.Bytes()
+       if err := binary.Write(buf, binary.LittleEndian, s); err != nil {
+               return nil, err
+       }
+       return buf.Bytes(), nil
 }
 
 // Unmarshal de-serializes the binary data and stores the result in the receiver.
@@ -92,10 +98,12 @@ type ConnectionParameterUpdateRequest struct {
 func (s ConnectionParameterUpdateRequest) Code() int { return 0x12 }
 
 // Marshal serializes the command parameters into binary form.
-func (s *ConnectionParameterUpdateRequest) Marshal() []byte {
+func (s *ConnectionParameterUpdateRequest) Marshal() ([]byte, error) {
        buf := bytes.NewBuffer(make([]byte, 0))
-       binary.Write(buf, binary.LittleEndian, s)
-       return buf.Bytes()
+       if err := binary.Write(buf, binary.LittleEndian, s); err != nil {
+               return nil, err
+       }
+       return buf.Bytes(), nil
 }
 
 // Unmarshal de-serializes the binary data and stores the result in the receiver.
@@ -115,10 +123,12 @@ type ConnectionParameterUpdateResponse struct {
 func (s ConnectionParameterUpdateResponse) Code() int { return 0x13 }
 
 // Marshal serializes the command parameters into binary form.
-func (s *ConnectionParameterUpdateResponse) Marshal() []byte {
+func (s *ConnectionParameterUpdateResponse) Marshal() ([]byte, error) {
        buf := bytes.NewBuffer(make([]byte, 0))
-       binary.Write(buf, binary.LittleEndian, s)
-       return buf.Bytes()
+       if err := binary.Write(buf, binary.LittleEndian, s); err != nil {
+               return nil, err
+       }
+       return buf.Bytes(), nil
 }
 
 // Unmarshal de-serializes the binary data and stores the result in the receiver.
@@ -142,10 +152,12 @@ type LECreditBasedConnectionRequest struct {
 func (s LECreditBasedConnectionRequest) Code() int { return 0x14 }
 
 // Marshal serializes the command parameters into binary form.
-func (s *LECreditBasedConnectionRequest) Marshal() []byte {
+func (s *LECreditBasedConnectionRequest) Marshal() ([]byte, error) {
        buf := bytes.NewBuffer(make([]byte, 0))
-       binary.Write(buf, binary.LittleEndian, s)
-       return buf.Bytes()
+       if err := binary.Write(buf, binary.LittleEndian, s); err != nil {
+               return nil, err
+       }
+       return buf.Bytes(), nil
 }
 
 // Unmarshal de-serializes the binary data and stores the result in the receiver.
@@ -169,10 +181,12 @@ type LECreditBasedConnectionResponse struct {
 func (s LECreditBasedConnectionResponse) Code() int { return 0x15 }
 
 // Marshal serializes the command parameters into binary form.
-func (s *LECreditBasedConnectionResponse) Marshal() []byte {
+func (s *LECreditBasedConnectionResponse) Marshal() ([]byte, error) {
        buf := bytes.NewBuffer(make([]byte, 0))
-       binary.Write(buf, binary.LittleEndian, s)
-       return buf.Bytes()
+       if err := binary.Write(buf, binary.LittleEndian, s); err != nil {
+               return nil, err
+       }
+       return buf.Bytes(), nil
 }
 
 // Unmarshal de-serializes the binary data and stores the result in the receiver.
@@ -193,10 +207,12 @@ type LEFlowControlCredit struct {
 func (s LEFlowControlCredit) Code() int { return 0x16 }
 
 // Marshal serializes the command parameters into binary form.
-func (s *LEFlowControlCredit) Marshal() []byte {
+func (s *LEFlowControlCredit) Marshal() ([]byte, error) {
        buf := bytes.NewBuffer(make([]byte, 0))
-       binary.Write(buf, binary.LittleEndian, s)
-       return buf.Bytes()
+       if err := binary.Write(buf, binary.LittleEndian, s); err != nil {
+               return nil, err
+       }
+       return buf.Bytes(), nil
 }
 
 // Unmarshal de-serializes the binary data and stores the result in the receiver.
index 87a43f8..8e3950e 100644 (file)
@@ -25,9 +25,15 @@ const (
 
 func (c *Conn) sendSMP(p pdu) error {
        buf := bytes.NewBuffer(make([]byte, 0))
-       binary.Write(buf, binary.LittleEndian, uint16(4+len(p)))
-       binary.Write(buf, binary.LittleEndian, cidSMP)
-       binary.Write(buf, binary.LittleEndian, p)
+       if err := binary.Write(buf, binary.LittleEndian, uint16(4+len(p))); err != nil {
+               return err
+       }
+       if err := binary.Write(buf, binary.LittleEndian, cidSMP); err != nil {
+               return err
+       }
+       if err := binary.Write(buf, binary.LittleEndian, p); err != nil {
+               return err
+       }
        _, err := c.writePDU(buf.Bytes())
        logger.Debug("smp", "send", fmt.Sprintf("[%X]", buf.Bytes()))
        return err
diff --git a/vendor/github.com/go-ble/ble/option.go b/vendor/github.com/go-ble/ble/option.go
new file mode 100644 (file)
index 0000000..f301c7f
--- /dev/null
@@ -0,0 +1,68 @@
+package ble
+
+import (
+       "time"
+
+       "github.com/go-ble/ble/linux/hci/cmd"
+)
+
+// DeviceOption is an interface which the device should implement to allow using configuration options
+type DeviceOption interface {
+       SetDeviceID(int) error
+       SetDialerTimeout(time.Duration) error
+       SetListenerTimeout(time.Duration) error
+       SetConnParams(cmd.LECreateConnection) error
+       SetPeripheralRole() error
+       SetCentralRole() error
+}
+
+// An Option is a configuration function, which configures the device.
+type Option func(DeviceOption) error
+
+// OptDeviceID sets HCI device ID.
+func OptDeviceID(id int) Option {
+       return func(opt DeviceOption) error {
+               opt.SetDeviceID(id)
+               return nil
+       }
+}
+
+// OptDialerTimeout sets dialing timeout for Dialer.
+func OptDialerTimeout(d time.Duration) Option {
+       return func(opt DeviceOption) error {
+               opt.SetDialerTimeout(d)
+               return nil
+       }
+}
+
+// OptListenerTimeout sets dialing timeout for Listener.
+func OptListenerTimeout(d time.Duration) Option {
+       return func(opt DeviceOption) error {
+               opt.SetListenerTimeout(d)
+               return nil
+       }
+}
+
+// OptConnParams overrides default connection parameters.
+func OptConnParams(param cmd.LECreateConnection) Option {
+       return func(opt DeviceOption) error {
+               opt.SetConnParams(param)
+               return nil
+       }
+}
+
+// OptPeripheralRole configures the device to perform Peripheral tasks.
+func OptPeripheralRole() Option {
+       return func(opt DeviceOption) error {
+               opt.SetPeripheralRole()
+               return nil
+       }
+}
+
+// OptCentralRole configures the device to perform Central tasks.
+func OptCentralRole() Option {
+       return func(opt DeviceOption) error {
+               opt.SetCentralRole()
+               return nil
+       }
+}
index 79ebf33..999c262 100644 (file)
@@ -84,15 +84,13 @@ func Contains(s []UUID, u UUID) bool {
 
 // Reverse returns a reversed copy of u.
 func Reverse(u []byte) []byte {
-       // Special-case 16 bit UUIDS for speed.
        l := len(u)
-       if l == 2 {
-               return []byte{u[1], u[0]}
-       }
        b := make([]byte, l)
-       for i := 0; i < l/2+1; i++ {
-               b[i], b[l-i-1] = u[l-i-1], u[i]
+
+       for i := 0; i < l; i++ {
+               b[l-i-1] = u[i]
        }
+
        return b
 }
 
diff --git a/vendor/github.com/go-ble/ble/uuid_test.go b/vendor/github.com/go-ble/ble/uuid_test.go
new file mode 100644 (file)
index 0000000..bb823d6
--- /dev/null
@@ -0,0 +1,28 @@
+package ble
+
+import (
+       "bytes"
+       "testing"
+)
+
+var forward = [][]byte{
+       []byte{1, 2, 3, 4, 5, 6},
+       []byte{12, 143, 231, 123, 87, 124, 209},
+       []byte{3, 43, 223, 12, 54},
+}
+
+var reverse = [][]byte{
+       []byte{6, 5, 4, 3, 2, 1},
+       []byte{209, 124, 87, 123, 231, 143, 12},
+       []byte{54, 12, 223, 43, 3},
+}
+
+func TestReverse(t *testing.T) {
+
+       for i := 0; i < len(forward); i++ {
+               r := Reverse(forward[i])
+               if !bytes.Equal(r, reverse[i]) {
+                       t.Errorf("Error: %v in reverse should be %v, but is: %v", forward[i], reverse[i], r)
+               }
+       }
+}
diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/README.md b/vendor/github.com/konsorten/go-windows-terminal-sequences/README.md
new file mode 100644 (file)
index 0000000..949b77e
--- /dev/null
@@ -0,0 +1,40 @@
+# Windows Terminal Sequences
+
+This library allow for enabling Windows terminal color support for Go.
+
+See [Console Virtual Terminal Sequences](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences) for details.
+
+## Usage
+
+```go
+import (
+       "syscall"
+       
+       sequences "github.com/konsorten/go-windows-terminal-sequences"
+)
+
+func main() {
+       sequences.EnableVirtualTerminalProcessing(syscall.Stdout, true)
+}
+
+```
+
+## Authors
+
+The tool is sponsored by the [marvin + konsorten GmbH](http://www.konsorten.de).
+
+We thank all the authors who provided code to this library:
+
+* Felix Kollmann
+
+## License
+
+(The MIT License)
+
+Copyright (c) 2018 marvin + konsorten GmbH (open-source@konsorten.de)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/license b/vendor/github.com/konsorten/go-windows-terminal-sequences/license
new file mode 100644 (file)
index 0000000..14127cd
--- /dev/null
@@ -0,0 +1,9 @@
+(The MIT License)
+
+Copyright (c) 2017 marvin + konsorten GmbH (open-source@konsorten.de)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go b/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go
new file mode 100644 (file)
index 0000000..ef18d8f
--- /dev/null
@@ -0,0 +1,36 @@
+// +build windows
+
+package sequences
+
+import (
+       "syscall"
+       "unsafe"
+)
+
+var (
+       kernel32Dll    *syscall.LazyDLL  = syscall.NewLazyDLL("Kernel32.dll")
+       setConsoleMode *syscall.LazyProc = kernel32Dll.NewProc("SetConsoleMode")
+)
+
+func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error {
+       const ENABLE_VIRTUAL_TERMINAL_PROCESSING uint32 = 0x4
+
+       var mode uint32
+       err := syscall.GetConsoleMode(syscall.Stdout, &mode)
+       if err != nil {
+               return err
+       }
+
+       if enable {
+               mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING
+       } else {
+               mode &^= ENABLE_VIRTUAL_TERMINAL_PROCESSING
+       }
+
+       ret, _, err := setConsoleMode.Call(uintptr(unsafe.Pointer(stream)), uintptr(mode))
+       if ret == 0 {
+               return err
+       }
+
+       return nil
+}
diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_test.go b/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences_test.go
new file mode 100644 (file)
index 0000000..aad41c5
--- /dev/null
@@ -0,0 +1,48 @@
+// +build windows
+
+package sequences
+
+import (
+       "fmt"
+       "os"
+       "syscall"
+       "testing"
+)
+
+func TestStdoutSequencesOn(t *testing.T) {
+       err := EnableVirtualTerminalProcessing(syscall.Stdout, true)
+       if err != nil {
+               t.Fatalf("Failed to enable VTP: %v", err)
+       }
+       defer EnableVirtualTerminalProcessing(syscall.Stdout, false)
+
+       fmt.Fprintf(os.Stdout, "\x1b[34mHello \x1b[35mWorld\x1b[0m!\n")
+}
+
+func TestStdoutSequencesOff(t *testing.T) {
+       err := EnableVirtualTerminalProcessing(syscall.Stdout, false)
+       if err != nil {
+               t.Fatalf("Failed to enable VTP: %v", err)
+       }
+
+       fmt.Fprintf(os.Stdout, "\x1b[34mHello \x1b[35mWorld\x1b[0m!\n")
+}
+
+func TestStderrSequencesOn(t *testing.T) {
+       err := EnableVirtualTerminalProcessing(syscall.Stderr, true)
+       if err != nil {
+               t.Fatalf("Failed to enable VTP: %v", err)
+       }
+       defer EnableVirtualTerminalProcessing(syscall.Stderr, false)
+
+       fmt.Fprintf(os.Stderr, "\x1b[34mHello \x1b[35mWorld\x1b[0m!\n")
+}
+
+func TestStderrSequencesOff(t *testing.T) {
+       err := EnableVirtualTerminalProcessing(syscall.Stderr, false)
+       if err != nil {
+               t.Fatalf("Failed to enable VTP: %v", err)
+       }
+
+       fmt.Fprintf(os.Stderr, "\x1b[34mHello \x1b[35mWorld\x1b[0m!\n")
+}
diff --git a/vendor/github.com/kr/pretty/go.mod b/vendor/github.com/kr/pretty/go.mod
new file mode 100644 (file)
index 0000000..1e29533
--- /dev/null
@@ -0,0 +1,3 @@
+module "github.com/kr/pretty"
+
+require "github.com/kr/text" v0.1.0
index 404fe76..d9b0b01 100644 (file)
@@ -697,6 +697,10 @@ func TestStringMapStringSliceE(t *testing.T) {
        var stringMapInterface1 = map[string]interface{}{"key 1": []string{"value 1"}, "key 2": []string{"value 2"}}
        var stringMapInterfaceResult1 = map[string][]string{"key 1": {"value 1"}, "key 2": {"value 2"}}
 
+       var jsonStringMapString = `{"key 1": "value 1", "key 2": "value 2"}`
+       var jsonStringMapStringArray = `{"key 1": ["value 1"], "key 2": ["value 2", "value 3"]}`
+       var jsonStringMapStringArrayResult = map[string][]string{"key 1": {"value 1"}, "key 2": {"value 2", "value 3"}}
+
        type Key struct {
                k string
        }
@@ -718,11 +722,15 @@ func TestStringMapStringSliceE(t *testing.T) {
                {interfaceMapInterfaceSlice, stringMapStringSlice, false},
                {interfaceMapString, stringMapStringSingleSliceFieldsResult, false},
                {interfaceMapInterface, stringMapStringSingleSliceFieldsResult, false},
+               {jsonStringMapStringArray, jsonStringMapStringArrayResult, false},
+
                // errors
                {nil, nil, true},
                {testing.T{}, nil, true},
                {map[interface{}]interface{}{"foo": testing.T{}}, nil, true},
                {map[interface{}]interface{}{Key{"foo"}: "bar"}, nil, true}, // ToStringE(Key{"foo"}) should fail
+               {jsonStringMapString, nil, true},
+               {"", nil, true},
        }
 
        for i, test := range tests {
@@ -751,9 +759,13 @@ func TestToStringMapE(t *testing.T) {
        }{
                {map[interface{}]interface{}{"tag": "tags", "group": "groups"}, map[string]interface{}{"tag": "tags", "group": "groups"}, false},
                {map[string]interface{}{"tag": "tags", "group": "groups"}, map[string]interface{}{"tag": "tags", "group": "groups"}, false},
+               {`{"tag": "tags", "group": "groups"}`, map[string]interface{}{"tag": "tags", "group": "groups"}, false},
+               {`{"tag": "tags", "group": true}`, map[string]interface{}{"tag": "tags", "group": true}, false},
+
                // errors
                {nil, nil, true},
                {testing.T{}, nil, true},
+               {"", nil, true},
        }
 
        for i, test := range tests {
@@ -783,9 +795,12 @@ func TestToStringMapBoolE(t *testing.T) {
                {map[interface{}]interface{}{"v1": true, "v2": false}, map[string]bool{"v1": true, "v2": false}, false},
                {map[string]interface{}{"v1": true, "v2": false}, map[string]bool{"v1": true, "v2": false}, false},
                {map[string]bool{"v1": true, "v2": false}, map[string]bool{"v1": true, "v2": false}, false},
+               {`{"v1": true, "v2": false}`, map[string]bool{"v1": true, "v2": false}, false},
+
                // errors
                {nil, nil, true},
                {testing.T{}, nil, true},
+               {"", nil, true},
        }
 
        for i, test := range tests {
@@ -811,6 +826,9 @@ func TestToStringMapStringE(t *testing.T) {
        var stringMapInterface = map[string]interface{}{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"}
        var interfaceMapString = map[interface{}]string{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"}
        var interfaceMapInterface = map[interface{}]interface{}{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"}
+       var jsonString = `{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"}`
+       var invalidJsonString = `{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"`
+       var emptyString = ""
 
        tests := []struct {
                input  interface{}
@@ -821,9 +839,13 @@ func TestToStringMapStringE(t *testing.T) {
                {stringMapInterface, stringMapString, false},
                {interfaceMapString, stringMapString, false},
                {interfaceMapInterface, stringMapString, false},
+               {jsonString, stringMapString, false},
+
                // errors
                {nil, nil, true},
                {testing.T{}, nil, true},
+               {invalidJsonString, nil, true},
+               {emptyString, nil, true},
        }
 
        for i, test := range tests {
@@ -984,9 +1006,12 @@ func TestToDurationSliceE(t *testing.T) {
                {[]string{"1s", "1m"}, []time.Duration{time.Second, time.Minute}, false},
                {[]int{1, 2}, []time.Duration{1, 2}, false},
                {[]interface{}{1, 3}, []time.Duration{1, 3}, false},
+               {[]time.Duration{1, 3}, []time.Duration{1, 3}, false},
+
                // errors
                {nil, nil, true},
                {testing.T{}, nil, true},
+               {[]string{"invalid"}, nil, true},
        }
 
        for i, test := range tests {
index 81511fe..4fe1928 100644 (file)
@@ -6,6 +6,7 @@
 package cast
 
 import (
+       "encoding/json"
        "errors"
        "fmt"
        "html/template"
@@ -872,6 +873,9 @@ func ToStringMapStringE(i interface{}) (map[string]string, error) {
                        m[ToString(k)] = ToString(val)
                }
                return m, nil
+       case string:
+               err := jsonStringToObject(v, &m)
+               return m, err
        default:
                return m, fmt.Errorf("unable to cast %#v of type %T to map[string]string", i, i)
        }
@@ -932,6 +936,9 @@ func ToStringMapStringSliceE(i interface{}) (map[string][]string, error) {
                        }
                        m[key] = value
                }
+       case string:
+               err := jsonStringToObject(v, &m)
+               return m, err
        default:
                return m, fmt.Errorf("unable to cast %#v of type %T to map[string][]string", i, i)
        }
@@ -955,6 +962,9 @@ func ToStringMapBoolE(i interface{}) (map[string]bool, error) {
                return m, nil
        case map[string]bool:
                return v, nil
+       case string:
+               err := jsonStringToObject(v, &m)
+               return m, err
        default:
                return m, fmt.Errorf("unable to cast %#v of type %T to map[string]bool", i, i)
        }
@@ -972,6 +982,9 @@ func ToStringMapE(i interface{}) (map[string]interface{}, error) {
                return m, nil
        case map[string]interface{}:
                return v, nil
+       case string:
+               err := jsonStringToObject(v, &m)
+               return m, err
        default:
                return m, fmt.Errorf("unable to cast %#v of type %T to map[string]interface{}", i, i)
        }
@@ -1144,3 +1157,10 @@ func parseDateWith(s string, dates []string) (d time.Time, e error) {
        }
        return d, fmt.Errorf("unable to parse date: %s", s)
 }
+
+// jsonStringToObject attempts to unmarshall a string as JSON into
+// the object passed as pointer.
+func jsonStringToObject(s string, v interface{}) error {
+       data := []byte(s)
+       return json.Unmarshal(data, v)
+}
diff --git a/vendor/github.com/spf13/pflag/bytes.go b/vendor/github.com/spf13/pflag/bytes.go
new file mode 100644 (file)
index 0000000..67d5304
--- /dev/null
@@ -0,0 +1,209 @@
+package pflag
+
+import (
+       "encoding/base64"
+       "encoding/hex"
+       "fmt"
+       "strings"
+)
+
+// BytesHex adapts []byte for use as a flag. Value of flag is HEX encoded
+type bytesHexValue []byte
+
+// String implements pflag.Value.String.
+func (bytesHex bytesHexValue) String() string {
+       return fmt.Sprintf("%X", []byte(bytesHex))
+}
+
+// Set implements pflag.Value.Set.
+func (bytesHex *bytesHexValue) Set(value string) error {
+       bin, err := hex.DecodeString(strings.TrimSpace(value))
+
+       if err != nil {
+               return err
+       }
+
+       *bytesHex = bin
+
+       return nil
+}
+
+// Type implements pflag.Value.Type.
+func (*bytesHexValue) Type() string {
+       return "bytesHex"
+}
+
+func newBytesHexValue(val []byte, p *[]byte) *bytesHexValue {
+       *p = val
+       return (*bytesHexValue)(p)
+}
+
+func bytesHexConv(sval string) (interface{}, error) {
+
+       bin, err := hex.DecodeString(sval)
+
+       if err == nil {
+               return bin, nil
+       }
+
+       return nil, fmt.Errorf("invalid string being converted to Bytes: %s %s", sval, err)
+}
+
+// GetBytesHex return the []byte value of a flag with the given name
+func (f *FlagSet) GetBytesHex(name string) ([]byte, error) {
+       val, err := f.getFlagType(name, "bytesHex", bytesHexConv)
+
+       if err != nil {
+               return []byte{}, err
+       }
+
+       return val.([]byte), nil
+}
+
+// BytesHexVar defines an []byte flag with specified name, default value, and usage string.
+// The argument p points to an []byte variable in which to store the value of the flag.
+func (f *FlagSet) BytesHexVar(p *[]byte, name string, value []byte, usage string) {
+       f.VarP(newBytesHexValue(value, p), name, "", usage)
+}
+
+// BytesHexVarP is like BytesHexVar, but accepts a shorthand letter that can be used after a single dash.
+func (f *FlagSet) BytesHexVarP(p *[]byte, name, shorthand string, value []byte, usage string) {
+       f.VarP(newBytesHexValue(value, p), name, shorthand, usage)
+}
+
+// BytesHexVar defines an []byte flag with specified name, default value, and usage string.
+// The argument p points to an []byte variable in which to store the value of the flag.
+func BytesHexVar(p *[]byte, name string, value []byte, usage string) {
+       CommandLine.VarP(newBytesHexValue(value, p), name, "", usage)
+}
+
+// BytesHexVarP is like BytesHexVar, but accepts a shorthand letter that can be used after a single dash.
+func BytesHexVarP(p *[]byte, name, shorthand string, value []byte, usage string) {
+       CommandLine.VarP(newBytesHexValue(value, p), name, shorthand, usage)
+}
+
+// BytesHex defines an []byte flag with specified name, default value, and usage string.
+// The return value is the address of an []byte variable that stores the value of the flag.
+func (f *FlagSet) BytesHex(name string, value []byte, usage string) *[]byte {
+       p := new([]byte)
+       f.BytesHexVarP(p, name, "", value, usage)
+       return p
+}
+
+// BytesHexP is like BytesHex, but accepts a shorthand letter that can be used after a single dash.
+func (f *FlagSet) BytesHexP(name, shorthand string, value []byte, usage string) *[]byte {
+       p := new([]byte)
+       f.BytesHexVarP(p, name, shorthand, value, usage)
+       return p
+}
+
+// BytesHex defines an []byte flag with specified name, default value, and usage string.
+// The return value is the address of an []byte variable that stores the value of the flag.
+func BytesHex(name string, value []byte, usage string) *[]byte {
+       return CommandLine.BytesHexP(name, "", value, usage)
+}
+
+// BytesHexP is like BytesHex, but accepts a shorthand letter that can be used after a single dash.
+func BytesHexP(name, shorthand string, value []byte, usage string) *[]byte {
+       return CommandLine.BytesHexP(name, shorthand, value, usage)
+}
+
+// BytesBase64 adapts []byte for use as a flag. Value of flag is Base64 encoded
+type bytesBase64Value []byte
+
+// String implements pflag.Value.String.
+func (bytesBase64 bytesBase64Value) String() string {
+       return base64.StdEncoding.EncodeToString([]byte(bytesBase64))
+}
+
+// Set implements pflag.Value.Set.
+func (bytesBase64 *bytesBase64Value) Set(value string) error {
+       bin, err := base64.StdEncoding.DecodeString(strings.TrimSpace(value))
+
+       if err != nil {
+               return err
+       }
+
+       *bytesBase64 = bin
+
+       return nil
+}
+
+// Type implements pflag.Value.Type.
+func (*bytesBase64Value) Type() string {
+       return "bytesBase64"
+}
+
+func newBytesBase64Value(val []byte, p *[]byte) *bytesBase64Value {
+       *p = val
+       return (*bytesBase64Value)(p)
+}
+
+func bytesBase64ValueConv(sval string) (interface{}, error) {
+
+       bin, err := base64.StdEncoding.DecodeString(sval)
+       if err == nil {
+               return bin, nil
+       }
+
+       return nil, fmt.Errorf("invalid string being converted to Bytes: %s %s", sval, err)
+}
+
+// GetBytesBase64 return the []byte value of a flag with the given name
+func (f *FlagSet) GetBytesBase64(name string) ([]byte, error) {
+       val, err := f.getFlagType(name, "bytesBase64", bytesBase64ValueConv)
+
+       if err != nil {
+               return []byte{}, err
+       }
+
+       return val.([]byte), nil
+}
+
+// BytesBase64Var defines an []byte flag with specified name, default value, and usage string.
+// The argument p points to an []byte variable in which to store the value of the flag.
+func (f *FlagSet) BytesBase64Var(p *[]byte, name string, value []byte, usage string) {
+       f.VarP(newBytesBase64Value(value, p), name, "", usage)
+}
+
+// BytesBase64VarP is like BytesBase64Var, but accepts a shorthand letter that can be used after a single dash.
+func (f *FlagSet) BytesBase64VarP(p *[]byte, name, shorthand string, value []byte, usage string) {
+       f.VarP(newBytesBase64Value(value, p), name, shorthand, usage)
+}
+
+// BytesBase64Var defines an []byte flag with specified name, default value, and usage string.
+// The argument p points to an []byte variable in which to store the value of the flag.
+func BytesBase64Var(p *[]byte, name string, value []byte, usage string) {
+       CommandLine.VarP(newBytesBase64Value(value, p), name, "", usage)
+}
+
+// BytesBase64VarP is like BytesBase64Var, but accepts a shorthand letter that can be used after a single dash.
+func BytesBase64VarP(p *[]byte, name, shorthand string, value []byte, usage string) {
+       CommandLine.VarP(newBytesBase64Value(value, p), name, shorthand, usage)
+}
+
+// BytesBase64 defines an []byte flag with specified name, default value, and usage string.
+// The return value is the address of an []byte variable that stores the value of the flag.
+func (f *FlagSet) BytesBase64(name string, value []byte, usage string) *[]byte {
+       p := new([]byte)
+       f.BytesBase64VarP(p, name, "", value, usage)
+       return p
+}
+
+// BytesBase64P is like BytesBase64, but accepts a shorthand letter that can be used after a single dash.
+func (f *FlagSet) BytesBase64P(name, shorthand string, value []byte, usage string) *[]byte {
+       p := new([]byte)
+       f.BytesBase64VarP(p, name, shorthand, value, usage)
+       return p
+}
+
+// BytesBase64 defines an []byte flag with specified name, default value, and usage string.
+// The return value is the address of an []byte variable that stores the value of the flag.
+func BytesBase64(name string, value []byte, usage string) *[]byte {
+       return CommandLine.BytesBase64P(name, "", value, usage)
+}
+
+// BytesBase64P is like BytesBase64, but accepts a shorthand letter that can be used after a single dash.
+func BytesBase64P(name, shorthand string, value []byte, usage string) *[]byte {
+       return CommandLine.BytesBase64P(name, shorthand, value, usage)
+}
diff --git a/vendor/github.com/spf13/pflag/bytes_test.go b/vendor/github.com/spf13/pflag/bytes_test.go
new file mode 100644 (file)
index 0000000..5251f34
--- /dev/null
@@ -0,0 +1,134 @@
+package pflag
+
+import (
+       "encoding/base64"
+       "fmt"
+       "os"
+       "testing"
+)
+
+func setUpBytesHex(bytesHex *[]byte) *FlagSet {
+       f := NewFlagSet("test", ContinueOnError)
+       f.BytesHexVar(bytesHex, "bytes", []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, "Some bytes in HEX")
+       f.BytesHexVarP(bytesHex, "bytes2", "B", []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, "Some bytes in HEX")
+       return f
+}
+
+func TestBytesHex(t *testing.T) {
+       testCases := []struct {
+               input    string
+               success  bool
+               expected string
+       }{
+               /// Positive cases
+               {"", true, ""}, // Is empty string OK ?
+               {"01", true, "01"},
+               {"0101", true, "0101"},
+               {"1234567890abcdef", true, "1234567890ABCDEF"},
+               {"1234567890ABCDEF", true, "1234567890ABCDEF"},
+
+               // Negative cases
+               {"0", false, ""},   // Short string
+               {"000", false, ""}, /// Odd-length string
+               {"qq", false, ""},  /// non-hex character
+       }
+
+       devnull, _ := os.Open(os.DevNull)
+       os.Stderr = devnull
+
+       for i := range testCases {
+               var bytesHex []byte
+               f := setUpBytesHex(&bytesHex)
+
+               tc := &testCases[i]
+
+               // --bytes
+               args := []string{
+                       fmt.Sprintf("--bytes=%s", tc.input),
+                       fmt.Sprintf("-B  %s", tc.input),
+                       fmt.Sprintf("--bytes2=%s", tc.input),
+               }
+
+               for _, arg := range args {
+                       err := f.Parse([]string{arg})
+
+                       if err != nil && tc.success == true {
+                               t.Errorf("expected success, got %q", err)
+                               continue
+                       } else if err == nil && tc.success == false {
+                               // bytesHex, err := f.GetBytesHex("bytes")
+                               t.Errorf("expected failure while processing %q", tc.input)
+                               continue
+                       } else if tc.success {
+                               bytesHex, err := f.GetBytesHex("bytes")
+                               if err != nil {
+                                       t.Errorf("Got error trying to fetch the 'bytes' flag: %v", err)
+                               }
+                               if fmt.Sprintf("%X", bytesHex) != tc.expected {
+                                       t.Errorf("expected %q, got '%X'", tc.expected, bytesHex)
+                               }
+                       }
+               }
+       }
+}
+
+func setUpBytesBase64(bytesBase64 *[]byte) *FlagSet {
+       f := NewFlagSet("test", ContinueOnError)
+       f.BytesBase64Var(bytesBase64, "bytes", []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, "Some bytes in Base64")
+       f.BytesBase64VarP(bytesBase64, "bytes2", "B", []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, "Some bytes in Base64")
+       return f
+}
+
+func TestBytesBase64(t *testing.T) {
+       testCases := []struct {
+               input    string
+               success  bool
+               expected string
+       }{
+               /// Positive cases
+               {"", true, ""}, // Is empty string OK ?
+               {"AQ==", true, "AQ=="},
+
+               // Negative cases
+               {"AQ", false, ""}, // Padding removed
+               {"ï", false, ""},  // non-base64 characters
+       }
+
+       devnull, _ := os.Open(os.DevNull)
+       os.Stderr = devnull
+
+       for i := range testCases {
+               var bytesBase64 []byte
+               f := setUpBytesBase64(&bytesBase64)
+
+               tc := &testCases[i]
+
+               // --bytes
+               args := []string{
+                       fmt.Sprintf("--bytes=%s", tc.input),
+                       fmt.Sprintf("-B  %s", tc.input),
+                       fmt.Sprintf("--bytes2=%s", tc.input),
+               }
+
+               for _, arg := range args {
+                       err := f.Parse([]string{arg})
+
+                       if err != nil && tc.success == true {
+                               t.Errorf("expected success, got %q", err)
+                               continue
+                       } else if err == nil && tc.success == false {
+                               // bytesBase64, err := f.GetBytesBase64("bytes")
+                               t.Errorf("expected failure while processing %q", tc.input)
+                               continue
+                       } else if tc.success {
+                               bytesBase64, err := f.GetBytesBase64("bytes")
+                               if err != nil {
+                                       t.Errorf("Got error trying to fetch the 'bytes' flag: %v", err)
+                               }
+                               if base64.StdEncoding.EncodeToString(bytesBase64) != tc.expected {
+                                       t.Errorf("expected %q, got '%X'", tc.expected, bytesBase64)
+                               }
+                       }
+               }
+       }
+}
index 250a438..aa126e4 100644 (file)
@@ -11,13 +11,13 @@ func newCountValue(val int, p *int) *countValue {
 }
 
 func (i *countValue) Set(s string) error {
-       v, err := strconv.ParseInt(s, 0, 64)
-       // -1 means that no specific value was passed, so increment
-       if v == -1 {
+       // "+1" means that no specific value was passed, so increment
+       if s == "+1" {
                *i = countValue(*i + 1)
-       } else {
-               *i = countValue(v)
+               return nil
        }
+       v, err := strconv.ParseInt(s, 0, 0)
+       *i = countValue(v)
        return err
 }
 
@@ -54,7 +54,7 @@ func (f *FlagSet) CountVar(p *int, name string, usage string) {
 // CountVarP is like CountVar only take a shorthand for the flag name.
 func (f *FlagSet) CountVarP(p *int, name, shorthand string, usage string) {
        flag := f.VarPF(newCountValue(0, p), name, shorthand, usage)
-       flag.NoOptDefVal = "-1"
+       flag.NoOptDefVal = "+1"
 }
 
 // CountVar like CountVar only the flag is placed on the CommandLine instead of a given flag set
index 460d96a..3785d37 100644 (file)
@@ -17,10 +17,14 @@ func TestCount(t *testing.T) {
                success  bool
                expected int
        }{
+               {[]string{}, true, 0},
+               {[]string{"-v"}, true, 1},
                {[]string{"-vvv"}, true, 3},
                {[]string{"-v", "-v", "-v"}, true, 3},
                {[]string{"-v", "--verbose", "-v"}, true, 3},
                {[]string{"-v=3", "-v"}, true, 4},
+               {[]string{"--verbose=0"}, true, 0},
+               {[]string{"-v=0"}, true, 0},
                {[]string{"-v=a"}, false, 0},
        }
 
@@ -45,7 +49,7 @@ func TestCount(t *testing.T) {
                                t.Errorf("Got error trying to fetch the counter flag")
                        }
                        if c != tc.expected {
-                               t.Errorf("expected %q, got %q", tc.expected, c)
+                               t.Errorf("expected %d, got %d", tc.expected, c)
                        }
                }
        }
diff --git a/vendor/github.com/spf13/pflag/duration_slice.go b/vendor/github.com/spf13/pflag/duration_slice.go
new file mode 100644 (file)
index 0000000..52c6b6d
--- /dev/null
@@ -0,0 +1,128 @@
+package pflag
+
+import (
+       "fmt"
+       "strings"
+       "time"
+)
+
+// -- durationSlice Value
+type durationSliceValue struct {
+       value   *[]time.Duration
+       changed bool
+}
+
+func newDurationSliceValue(val []time.Duration, p *[]time.Duration) *durationSliceValue {
+       dsv := new(durationSliceValue)
+       dsv.value = p
+       *dsv.value = val
+       return dsv
+}
+
+func (s *durationSliceValue) Set(val string) error {
+       ss := strings.Split(val, ",")
+       out := make([]time.Duration, len(ss))
+       for i, d := range ss {
+               var err error
+               out[i], err = time.ParseDuration(d)
+               if err != nil {
+                       return err
+               }
+
+       }
+       if !s.changed {
+               *s.value = out
+       } else {
+               *s.value = append(*s.value, out...)
+       }
+       s.changed = true
+       return nil
+}
+
+func (s *durationSliceValue) Type() string {
+       return "durationSlice"
+}
+
+func (s *durationSliceValue) String() string {
+       out := make([]string, len(*s.value))
+       for i, d := range *s.value {
+               out[i] = fmt.Sprintf("%s", d)
+       }
+       return "[" + strings.Join(out, ",") + "]"
+}
+
+func durationSliceConv(val string) (interface{}, error) {
+       val = strings.Trim(val, "[]")
+       // Empty string would cause a slice with one (empty) entry
+       if len(val) == 0 {
+               return []time.Duration{}, nil
+       }
+       ss := strings.Split(val, ",")
+       out := make([]time.Duration, len(ss))
+       for i, d := range ss {
+               var err error
+               out[i], err = time.ParseDuration(d)
+               if err != nil {
+                       return nil, err
+               }
+
+       }
+       return out, nil
+}
+
+// GetDurationSlice returns the []time.Duration value of a flag with the given name
+func (f *FlagSet) GetDurationSlice(name string) ([]time.Duration, error) {
+       val, err := f.getFlagType(name, "durationSlice", durationSliceConv)
+       if err != nil {
+               return []time.Duration{}, err
+       }
+       return val.([]time.Duration), nil
+}
+
+// DurationSliceVar defines a durationSlice flag with specified name, default value, and usage string.
+// The argument p points to a []time.Duration variable in which to store the value of the flag.
+func (f *FlagSet) DurationSliceVar(p *[]time.Duration, name string, value []time.Duration, usage string) {
+       f.VarP(newDurationSliceValue(value, p), name, "", usage)
+}
+
+// DurationSliceVarP is like DurationSliceVar, but accepts a shorthand letter that can be used after a single dash.
+func (f *FlagSet) DurationSliceVarP(p *[]time.Duration, name, shorthand string, value []time.Duration, usage string) {
+       f.VarP(newDurationSliceValue(value, p), name, shorthand, usage)
+}
+
+// DurationSliceVar defines a duration[] flag with specified name, default value, and usage string.
+// The argument p points to a duration[] variable in which to store the value of the flag.
+func DurationSliceVar(p *[]time.Duration, name string, value []time.Duration, usage string) {
+       CommandLine.VarP(newDurationSliceValue(value, p), name, "", usage)
+}
+
+// DurationSliceVarP is like DurationSliceVar, but accepts a shorthand letter that can be used after a single dash.
+func DurationSliceVarP(p *[]time.Duration, name, shorthand string, value []time.Duration, usage string) {
+       CommandLine.VarP(newDurationSliceValue(value, p), name, shorthand, usage)
+}
+
+// DurationSlice defines a []time.Duration flag with specified name, default value, and usage string.
+// The return value is the address of a []time.Duration variable that stores the value of the flag.
+func (f *FlagSet) DurationSlice(name string, value []time.Duration, usage string) *[]time.Duration {
+       p := []time.Duration{}
+       f.DurationSliceVarP(&p, name, "", value, usage)
+       return &p
+}
+
+// DurationSliceP is like DurationSlice, but accepts a shorthand letter that can be used after a single dash.
+func (f *FlagSet) DurationSliceP(name, shorthand string, value []time.Duration, usage string) *[]time.Duration {
+       p := []time.Duration{}
+       f.DurationSliceVarP(&p, name, shorthand, value, usage)
+       return &p
+}
+
+// DurationSlice defines a []time.Duration flag with specified name, default value, and usage string.
+// The return value is the address of a []time.Duration variable that stores the value of the flag.
+func DurationSlice(name string, value []time.Duration, usage string) *[]time.Duration {
+       return CommandLine.DurationSliceP(name, "", value, usage)
+}
+
+// DurationSliceP is like DurationSlice, but accepts a shorthand letter that can be used after a single dash.
+func DurationSliceP(name, shorthand string, value []time.Duration, usage string) *[]time.Duration {
+       return CommandLine.DurationSliceP(name, shorthand, value, usage)
+}
diff --git a/vendor/github.com/spf13/pflag/duration_slice_test.go b/vendor/github.com/spf13/pflag/duration_slice_test.go
new file mode 100644 (file)
index 0000000..489b012
--- /dev/null
@@ -0,0 +1,165 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code ds governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package pflag
+
+import (
+       "fmt"
+       "strings"
+       "testing"
+       "time"
+)
+
+func setUpDSFlagSet(dsp *[]time.Duration) *FlagSet {
+       f := NewFlagSet("test", ContinueOnError)
+       f.DurationSliceVar(dsp, "ds", []time.Duration{}, "Command separated list!")
+       return f
+}
+
+func setUpDSFlagSetWithDefault(dsp *[]time.Duration) *FlagSet {
+       f := NewFlagSet("test", ContinueOnError)
+       f.DurationSliceVar(dsp, "ds", []time.Duration{0, 1}, "Command separated list!")
+       return f
+}
+
+func TestEmptyDS(t *testing.T) {
+       var ds []time.Duration
+       f := setUpDSFlagSet(&ds)
+       err := f.Parse([]string{})
+       if err != nil {
+               t.Fatal("expected no error; got", err)
+       }
+
+       getDS, err := f.GetDurationSlice("ds")
+       if err != nil {
+               t.Fatal("got an error from GetDurationSlice():", err)
+       }
+       if len(getDS) != 0 {
+               t.Fatalf("got ds %v with len=%d but expected length=0", getDS, len(getDS))
+       }
+}
+
+func TestDS(t *testing.T) {
+       var ds []time.Duration
+       f := setUpDSFlagSet(&ds)
+
+       vals := []string{"1ns", "2ms", "3m", "4h"}
+       arg := fmt.Sprintf("--ds=%s", strings.Join(vals, ","))
+       err := f.Parse([]string{arg})
+       if err != nil {
+               t.Fatal("expected no error; got", err)
+       }
+       for i, v := range ds {
+               d, err := time.ParseDuration(vals[i])
+               if err != nil {
+                       t.Fatalf("got error: %v", err)
+               }
+               if d != v {
+                       t.Fatalf("expected ds[%d] to be %s but got: %d", i, vals[i], v)
+               }
+       }
+       getDS, err := f.GetDurationSlice("ds")
+       if err != nil {
+               t.Fatalf("got error: %v", err)
+       }
+       for i, v := range getDS {
+               d, err := time.ParseDuration(vals[i])
+               if err != nil {
+                       t.Fatalf("got error: %v", err)
+               }
+               if d != v {
+                       t.Fatalf("expected ds[%d] to be %s but got: %d from GetDurationSlice", i, vals[i], v)
+               }
+       }
+}
+
+func TestDSDefault(t *testing.T) {
+       var ds []time.Duration
+       f := setUpDSFlagSetWithDefault(&ds)
+
+       vals := []string{"0s", "1ns"}
+
+       err := f.Parse([]string{})
+       if err != nil {
+               t.Fatal("expected no error; got", err)
+       }
+       for i, v := range ds {
+               d, err := time.ParseDuration(vals[i])
+               if err != nil {
+                       t.Fatalf("got error: %v", err)
+               }
+               if d != v {
+                       t.Fatalf("expected ds[%d] to be %d but got: %d", i, d, v)
+               }
+       }
+
+       getDS, err := f.GetDurationSlice("ds")
+       if err != nil {
+               t.Fatal("got an error from GetDurationSlice():", err)
+       }
+       for i, v := range getDS {
+               d, err := time.ParseDuration(vals[i])
+               if err != nil {
+                       t.Fatal("got an error from GetDurationSlice():", err)
+               }
+               if d != v {
+                       t.Fatalf("expected ds[%d] to be %d from GetDurationSlice but got: %d", i, d, v)
+               }
+       }
+}
+
+func TestDSWithDefault(t *testing.T) {
+       var ds []time.Duration
+       f := setUpDSFlagSetWithDefault(&ds)
+
+       vals := []string{"1ns", "2ns"}
+       arg := fmt.Sprintf("--ds=%s", strings.Join(vals, ","))
+       err := f.Parse([]string{arg})
+       if err != nil {
+               t.Fatal("expected no error; got", err)
+       }
+       for i, v := range ds {
+               d, err := time.ParseDuration(vals[i])
+               if err != nil {
+                       t.Fatalf("got error: %v", err)
+               }
+               if d != v {
+                       t.Fatalf("expected ds[%d] to be %d but got: %d", i, d, v)
+               }
+       }
+
+       getDS, err := f.GetDurationSlice("ds")
+       if err != nil {
+               t.Fatal("got an error from GetDurationSlice():", err)
+       }
+       for i, v := range getDS {
+               d, err := time.ParseDuration(vals[i])
+               if err != nil {
+                       t.Fatalf("got error: %v", err)
+               }
+               if d != v {
+                       t.Fatalf("expected ds[%d] to be %d from GetDurationSlice but got: %d", i, d, v)
+               }
+       }
+}
+
+func TestDSCalledTwice(t *testing.T) {
+       var ds []time.Duration
+       f := setUpDSFlagSet(&ds)
+
+       in := []string{"1ns,2ns", "3ns"}
+       expected := []time.Duration{1, 2, 3}
+       argfmt := "--ds=%s"
+       arg1 := fmt.Sprintf(argfmt, in[0])
+       arg2 := fmt.Sprintf(argfmt, in[1])
+       err := f.Parse([]string{arg1, arg2})
+       if err != nil {
+               t.Fatal("expected no error; got", err)
+       }
+       for i, v := range ds {
+               if expected[i] != v {
+                       t.Fatalf("expected ds[%d] to be %d but got: %d", i, expected[i], v)
+               }
+       }
+}
index 6f1fc30..9beeda8 100644 (file)
@@ -101,6 +101,7 @@ package pflag
 import (
        "bytes"
        "errors"
+       goflag "flag"
        "fmt"
        "io"
        "os"
@@ -123,6 +124,12 @@ const (
        PanicOnError
 )
 
+// ParseErrorsWhitelist defines the parsing errors that can be ignored
+type ParseErrorsWhitelist struct {
+       // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags
+       UnknownFlags bool
+}
+
 // NormalizedName is a flag name that has been normalized according to rules
 // for the FlagSet (e.g. making '-' and '_' equivalent).
 type NormalizedName string
@@ -138,6 +145,9 @@ type FlagSet struct {
        // help/usage messages.
        SortFlags bool
 
+       // ParseErrorsWhitelist is used to configure a whitelist of errors
+       ParseErrorsWhitelist ParseErrorsWhitelist
+
        name              string
        parsed            bool
        actual            map[NormalizedName]*Flag
@@ -153,6 +163,8 @@ type FlagSet struct {
        output            io.Writer // nil means stderr; use out() accessor
        interspersed      bool      // allow interspersed option/non-option args
        normalizeNameFunc func(f *FlagSet, name string) NormalizedName
+
+       addedGoFlagSets []*goflag.FlagSet
 }
 
 // A Flag represents the state of a flag.
@@ -202,12 +214,18 @@ func sortFlags(flags map[NormalizedName]*Flag) []*Flag {
 func (f *FlagSet) SetNormalizeFunc(n func(f *FlagSet, name string) NormalizedName) {
        f.normalizeNameFunc = n
        f.sortedFormal = f.sortedFormal[:0]
-       for k, v := range f.orderedFormal {
-               delete(f.formal, NormalizedName(v.Name))
-               nname := f.normalizeFlagName(v.Name)
-               v.Name = string(nname)
-               f.formal[nname] = v
-               f.orderedFormal[k] = v
+       for fname, flag := range f.formal {
+               nname := f.normalizeFlagName(flag.Name)
+               if fname == nname {
+                       continue
+               }
+               flag.Name = string(nname)
+               delete(f.formal, fname)
+               f.formal[nname] = flag
+               if _, set := f.actual[fname]; set {
+                       delete(f.actual, fname)
+                       f.actual[nname] = flag
+               }
        }
 }
 
@@ -261,16 +279,16 @@ func (f *FlagSet) VisitAll(fn func(*Flag)) {
        }
 }
 
-// HasFlags returns a bool to indicate if the FlagSet has any flags definied.
+// HasFlags returns a bool to indicate if the FlagSet has any flags defined.
 func (f *FlagSet) HasFlags() bool {
        return len(f.formal) > 0
 }
 
 // HasAvailableFlags returns a bool to indicate if the FlagSet has any flags
-// definied that are not hidden or deprecated.
+// that are not hidden.
 func (f *FlagSet) HasAvailableFlags() bool {
        for _, flag := range f.formal {
-               if !flag.Hidden && len(flag.Deprecated) == 0 {
+               if !flag.Hidden {
                        return true
                }
        }
@@ -380,6 +398,7 @@ func (f *FlagSet) MarkDeprecated(name string, usageMessage string) error {
                return fmt.Errorf("deprecated message for flag %q must be set", name)
        }
        flag.Deprecated = usageMessage
+       flag.Hidden = true
        return nil
 }
 
@@ -440,13 +459,15 @@ func (f *FlagSet) Set(name, value string) error {
                return fmt.Errorf("invalid argument %q for %q flag: %v", value, flagName, err)
        }
 
-       if f.actual == nil {
-               f.actual = make(map[NormalizedName]*Flag)
-       }
-       f.actual[normalName] = flag
-       f.orderedActual = append(f.orderedActual, flag)
+       if !flag.Changed {
+               if f.actual == nil {
+                       f.actual = make(map[NormalizedName]*Flag)
+               }
+               f.actual[normalName] = flag
+               f.orderedActual = append(f.orderedActual, flag)
 
-       flag.Changed = true
+               flag.Changed = true
+       }
 
        if flag.Deprecated != "" {
                fmt.Fprintf(f.out(), "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated)
@@ -556,6 +577,14 @@ func UnquoteUsage(flag *Flag) (name string, usage string) {
                name = "int"
        case "uint64":
                name = "uint"
+       case "stringSlice":
+               name = "strings"
+       case "intSlice":
+               name = "ints"
+       case "uintSlice":
+               name = "uints"
+       case "boolSlice":
+               name = "bools"
        }
 
        return
@@ -570,11 +599,14 @@ func wrapN(i, slop int, s string) (string, string) {
                return s, ""
        }
 
-       w := strings.LastIndexAny(s[:i], " \t")
+       w := strings.LastIndexAny(s[:i], " \t\n")
        if w <= 0 {
                return s, ""
        }
-
+       nlPos := strings.LastIndex(s[:i], "\n")
+       if nlPos > 0 && nlPos < w {
+               return s[:nlPos], s[nlPos+1:]
+       }
        return s[:w], s[w+1:]
 }
 
@@ -583,7 +615,7 @@ func wrapN(i, slop int, s string) (string, string) {
 // caller). Pass `w` == 0 to do no wrapping
 func wrap(i, w int, s string) string {
        if w == 0 {
-               return s
+               return strings.Replace(s, "\n", "\n"+strings.Repeat(" ", i), -1)
        }
 
        // space between indent i and end of line width w into which
@@ -601,7 +633,7 @@ func wrap(i, w int, s string) string {
        }
        // If still not enough space then don't even try to wrap.
        if wrap < 24 {
-               return s
+               return strings.Replace(s, "\n", r, -1)
        }
 
        // Try to avoid short orphan words on the final line, by
@@ -613,14 +645,14 @@ func wrap(i, w int, s string) string {
        // Handle first line, which is indented by the caller (or the
        // special case above)
        l, s = wrapN(wrap, slop, s)
-       r = r + l
+       r = r + strings.Replace(l, "\n", "\n"+strings.Repeat(" ", i), -1)
 
        // Now wrap the rest
        for s != "" {
                var t string
 
                t, s = wrapN(wrap, slop, s)
-               r = r + "\n" + strings.Repeat(" ", i) + t
+               r = r + "\n" + strings.Repeat(" ", i) + strings.Replace(t, "\n", "\n"+strings.Repeat(" ", i), -1)
        }
 
        return r
@@ -637,7 +669,7 @@ func (f *FlagSet) FlagUsagesWrapped(cols int) string {
 
        maxlen := 0
        f.VisitAll(func(flag *Flag) {
-               if flag.Deprecated != "" || flag.Hidden {
+               if flag.Hidden {
                        return
                }
 
@@ -660,6 +692,10 @@ func (f *FlagSet) FlagUsagesWrapped(cols int) string {
                                if flag.NoOptDefVal != "true" {
                                        line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
                                }
+                       case "count":
+                               if flag.NoOptDefVal != "+1" {
+                                       line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
+                               }
                        default:
                                line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
                        }
@@ -680,6 +716,9 @@ func (f *FlagSet) FlagUsagesWrapped(cols int) string {
                                line += fmt.Sprintf(" (default %s)", flag.DefValue)
                        }
                }
+               if len(flag.Deprecated) != 0 {
+                       line += fmt.Sprintf(" (DEPRECATED: %s)", flag.Deprecated)
+               }
 
                lines = append(lines, line)
        })
@@ -857,8 +896,10 @@ func VarP(value Value, name, shorthand, usage string) {
 // returns the error.
 func (f *FlagSet) failf(format string, a ...interface{}) error {
        err := fmt.Errorf(format, a...)
-       fmt.Fprintln(f.out(), err)
-       f.usage()
+       if f.errorHandling != ContinueOnError {
+               fmt.Fprintln(f.out(), err)
+               f.usage()
+       }
        return err
 }
 
@@ -874,6 +915,28 @@ func (f *FlagSet) usage() {
        }
 }
 
+//--unknown (args will be empty)
+//--unknown --next-flag ... (args will be --next-flag ...)
+//--unknown arg ... (args will be arg ...)
+func stripUnknownFlagValue(args []string) []string {
+       if len(args) == 0 {
+               //--unknown
+               return args
+       }
+
+       first := args[0]
+       if len(first) > 0 && first[0] == '-' {
+               //--unknown --next-flag ...
+               return args
+       }
+
+       //--unknown arg ... (args will be arg ...)
+       if len(args) > 1 {
+               return args[1:]
+       }
+       return nil
+}
+
 func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []string, err error) {
        a = args
        name := s[2:]
@@ -885,13 +948,24 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin
        split := strings.SplitN(name, "=", 2)
        name = split[0]
        flag, exists := f.formal[f.normalizeFlagName(name)]
+
        if !exists {
-               if name == "help" { // special case for nice help message.
+               switch {
+               case name == "help":
                        f.usage()
                        return a, ErrHelp
+               case f.ParseErrorsWhitelist.UnknownFlags:
+                       // --unknown=unknownval arg ...
+                       // we do not want to lose arg in this case
+                       if len(split) >= 2 {
+                               return a, nil
+                       }
+
+                       return stripUnknownFlagValue(a), nil
+               default:
+                       err = f.failf("unknown flag: --%s", name)
+                       return
                }
-               err = f.failf("unknown flag: --%s", name)
-               return
        }
 
        var value string
@@ -912,27 +986,43 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin
        }
 
        err = fn(flag, value)
+       if err != nil {
+               f.failf(err.Error())
+       }
        return
 }
 
 func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parseFunc) (outShorts string, outArgs []string, err error) {
+       outArgs = args
+
        if strings.HasPrefix(shorthands, "test.") {
                return
        }
 
-       outArgs = args
        outShorts = shorthands[1:]
        c := shorthands[0]
 
        flag, exists := f.shorthands[c]
        if !exists {
-               if c == 'h' { // special case for nice help message.
+               switch {
+               case c == 'h':
                        f.usage()
                        err = ErrHelp
                        return
+               case f.ParseErrorsWhitelist.UnknownFlags:
+                       // '-f=arg arg ...'
+                       // we do not want to lose arg in this case
+                       if len(shorthands) > 2 && shorthands[1] == '=' {
+                               outShorts = ""
+                               return
+                       }
+
+                       outArgs = stripUnknownFlagValue(outArgs)
+                       return
+               default:
+                       err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands)
+                       return
                }
-               err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands)
-               return
        }
 
        var value string
@@ -962,6 +1052,9 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse
        }
 
        err = fn(flag, value)
+       if err != nil {
+               f.failf(err.Error())
+       }
        return
 }
 
@@ -1016,6 +1109,11 @@ func (f *FlagSet) parseArgs(args []string, fn parseFunc) (err error) {
 // are defined and before flags are accessed by the program.
 // The return value will be ErrHelp if -help was set but not defined.
 func (f *FlagSet) Parse(arguments []string) error {
+       if f.addedGoFlagSets != nil {
+               for _, goFlagSet := range f.addedGoFlagSets {
+                       goFlagSet.Parse(nil)
+               }
+       }
        f.parsed = true
 
        if len(arguments) < 0 {
@@ -1034,6 +1132,7 @@ func (f *FlagSet) Parse(arguments []string) error {
                case ContinueOnError:
                        return err
                case ExitOnError:
+                       fmt.Println(err)
                        os.Exit(2)
                case PanicOnError:
                        panic(err)
index c3def0f..7d02dbc 100644 (file)
@@ -106,8 +106,8 @@ func TestUsage(t *testing.T) {
        if GetCommandLine().Parse([]string{"--x"}) == nil {
                t.Error("parse did not fail for unknown flag")
        }
-       if !called {
-               t.Error("did not call Usage for unknown flag")
+       if called {
+               t.Error("did call Usage while using ContinueOnError")
        }
 }
 
@@ -168,6 +168,7 @@ func testParse(f *FlagSet, t *testing.T) {
        bool3Flag := f.Bool("bool3", false, "bool3 value")
        intFlag := f.Int("int", 0, "int value")
        int8Flag := f.Int8("int8", 0, "int value")
+       int16Flag := f.Int16("int16", 0, "int value")
        int32Flag := f.Int32("int32", 0, "int value")
        int64Flag := f.Int64("int64", 0, "int64 value")
        uintFlag := f.Uint("uint", 0, "uint value")
@@ -192,6 +193,7 @@ func testParse(f *FlagSet, t *testing.T) {
                "--bool3=false",
                "--int=22",
                "--int8=-8",
+               "--int16=-16",
                "--int32=-32",
                "--int64=0x23",
                "--uint", "24",
@@ -236,9 +238,15 @@ func testParse(f *FlagSet, t *testing.T) {
        if *int8Flag != -8 {
                t.Error("int8 flag should be 0x23, is ", *int8Flag)
        }
+       if *int16Flag != -16 {
+               t.Error("int16 flag should be -16, is ", *int16Flag)
+       }
        if v, err := f.GetInt8("int8"); err != nil || v != *int8Flag {
                t.Error("GetInt8 does not work.")
        }
+       if v, err := f.GetInt16("int16"); err != nil || v != *int16Flag {
+               t.Error("GetInt16 does not work.")
+       }
        if *int32Flag != -32 {
                t.Error("int32 flag should be 0x23, is ", *int32Flag)
        }
@@ -381,7 +389,83 @@ func testParseAll(f *FlagSet, t *testing.T) {
        }
        if !reflect.DeepEqual(got, want) {
                t.Errorf("f.ParseAll() fail to restore the args")
-               t.Errorf("Got: %v", got)
+               t.Errorf("Got:  %v", got)
+               t.Errorf("Want: %v", want)
+       }
+}
+
+func testParseWithUnknownFlags(f *FlagSet, t *testing.T) {
+       if f.Parsed() {
+               t.Error("f.Parse() = true before Parse")
+       }
+       f.ParseErrorsWhitelist.UnknownFlags = true
+
+       f.BoolP("boola", "a", false, "bool value")
+       f.BoolP("boolb", "b", false, "bool2 value")
+       f.BoolP("boolc", "c", false, "bool3 value")
+       f.BoolP("boold", "d", false, "bool4 value")
+       f.BoolP("boole", "e", false, "bool4 value")
+       f.StringP("stringa", "s", "0", "string value")
+       f.StringP("stringz", "z", "0", "string value")
+       f.StringP("stringx", "x", "0", "string value")
+       f.StringP("stringy", "y", "0", "string value")
+       f.StringP("stringo", "o", "0", "string value")
+       f.Lookup("stringx").NoOptDefVal = "1"
+       args := []string{
+               "-ab",
+               "-cs=xx",
+               "--stringz=something",
+               "--unknown1",
+               "unknown1Value",
+               "-d=true",
+               "-x",
+               "--unknown2=unknown2Value",
+               "-u=unknown3Value",
+               "-p",
+               "unknown4Value",
+               "-q", //another unknown with bool value
+               "-y",
+               "ee",
+               "--unknown7=unknown7value",
+               "--stringo=ovalue",
+               "--unknown8=unknown8value",
+               "--boole",
+               "--unknown6",
+               "",
+               "-uuuuu",
+               "",
+               "--unknown10",
+               "--unknown11",
+       }
+       want := []string{
+               "boola", "true",
+               "boolb", "true",
+               "boolc", "true",
+               "stringa", "xx",
+               "stringz", "something",
+               "boold", "true",
+               "stringx", "1",
+               "stringy", "ee",
+               "stringo", "ovalue",
+               "boole", "true",
+       }
+       got := []string{}
+       store := func(flag *Flag, value string) error {
+               got = append(got, flag.Name)
+               if len(value) > 0 {
+                       got = append(got, value)
+               }
+               return nil
+       }
+       if err := f.ParseAll(args, store); err != nil {
+               t.Errorf("expected no error, got %s", err)
+       }
+       if !f.Parsed() {
+               t.Errorf("f.Parse() = false after Parse")
+       }
+       if !reflect.DeepEqual(got, want) {
+               t.Errorf("f.ParseAll() fail to restore the args")
+               t.Errorf("Got:  %v", got)
                t.Errorf("Want: %v", want)
        }
 }
@@ -492,6 +576,11 @@ func TestParseAll(t *testing.T) {
        testParseAll(GetCommandLine(), t)
 }
 
+func TestIgnoreUnknownFlags(t *testing.T) {
+       ResetForTesting(func() { t.Error("bad parse") })
+       testParseWithUnknownFlags(GetCommandLine(), t)
+}
+
 func TestFlagSetParse(t *testing.T) {
        testParse(NewFlagSet("test", ContinueOnError), t)
 }
@@ -604,7 +693,6 @@ func aliasAndWordSepFlagNames(f *FlagSet, name string) NormalizedName {
        switch name {
        case oldName:
                name = newName
-               break
        }
 
        return NormalizedName(name)
@@ -658,6 +746,70 @@ func TestNormalizationFuncShouldChangeFlagName(t *testing.T) {
        }
 }
 
+// Related to https://github.com/spf13/cobra/issues/521.
+func TestNormalizationSharedFlags(t *testing.T) {
+       f := NewFlagSet("set f", ContinueOnError)
+       g := NewFlagSet("set g", ContinueOnError)
+       nfunc := wordSepNormalizeFunc
+       testName := "valid_flag"
+       normName := nfunc(nil, testName)
+       if testName == string(normName) {
+               t.Error("TestNormalizationSharedFlags meaningless: the original and normalized flag names are identical:", testName)
+       }
+
+       f.Bool(testName, false, "bool value")
+       g.AddFlagSet(f)
+
+       f.SetNormalizeFunc(nfunc)
+       g.SetNormalizeFunc(nfunc)
+
+       if len(f.formal) != 1 {
+               t.Error("Normalizing flags should not result in duplications in the flag set:", f.formal)
+       }
+       if f.orderedFormal[0].Name != string(normName) {
+               t.Error("Flag name not normalized")
+       }
+       for k := range f.formal {
+               if k != "valid.flag" {
+                       t.Errorf("The key in the flag map should have been normalized: wanted \"%s\", got \"%s\" instead", normName, k)
+               }
+       }
+
+       if !reflect.DeepEqual(f.formal, g.formal) || !reflect.DeepEqual(f.orderedFormal, g.orderedFormal) {
+               t.Error("Two flag sets sharing the same flags should stay consistent after being normalized. Original set:", f.formal, "Duplicate set:", g.formal)
+       }
+}
+
+func TestNormalizationSetFlags(t *testing.T) {
+       f := NewFlagSet("normalized", ContinueOnError)
+       nfunc := wordSepNormalizeFunc
+       testName := "valid_flag"
+       normName := nfunc(nil, testName)
+       if testName == string(normName) {
+               t.Error("TestNormalizationSetFlags meaningless: the original and normalized flag names are identical:", testName)
+       }
+
+       f.Bool(testName, false, "bool value")
+       f.Set(testName, "true")
+       f.SetNormalizeFunc(nfunc)
+
+       if len(f.formal) != 1 {
+               t.Error("Normalizing flags should not result in duplications in the flag set:", f.formal)
+       }
+       if f.orderedFormal[0].Name != string(normName) {
+               t.Error("Flag name not normalized")
+       }
+       for k := range f.formal {
+               if k != "valid.flag" {
+                       t.Errorf("The key in the flag map should have been normalized: wanted \"%s\", got \"%s\" instead", normName, k)
+               }
+       }
+
+       if !reflect.DeepEqual(f.formal, f.actual) {
+               t.Error("The map of set flags should get normalized. Formal:", f.formal, "Actual:", f.actual)
+       }
+}
+
 // Declare a user-defined flag type.
 type flagVar []string
 
@@ -819,10 +971,14 @@ func TestTermination(t *testing.T) {
        }
 }
 
-func TestDeprecatedFlagInDocs(t *testing.T) {
+func getDeprecatedFlagSet() *FlagSet {
        f := NewFlagSet("bob", ContinueOnError)
        f.Bool("badflag", true, "always true")
        f.MarkDeprecated("badflag", "use --good-flag instead")
+       return f
+}
+func TestDeprecatedFlagInDocs(t *testing.T) {
+       f := getDeprecatedFlagSet()
 
        out := new(bytes.Buffer)
        f.SetOutput(out)
@@ -833,6 +989,27 @@ func TestDeprecatedFlagInDocs(t *testing.T) {
        }
 }
 
+func TestUnHiddenDeprecatedFlagInDocs(t *testing.T) {
+       f := getDeprecatedFlagSet()
+       flg := f.Lookup("badflag")
+       if flg == nil {
+               t.Fatalf("Unable to lookup 'bob' in TestUnHiddenDeprecatedFlagInDocs")
+       }
+       flg.Hidden = false
+
+       out := new(bytes.Buffer)
+       f.SetOutput(out)
+       f.PrintDefaults()
+
+       defaults := out.String()
+       if !strings.Contains(defaults, "badflag") {
+               t.Errorf("Did not find deprecated flag in usage!")
+       }
+       if !strings.Contains(defaults, "use --good-flag instead") {
+               t.Errorf("Did not find 'use --good-flag instead' in defaults")
+       }
+}
+
 func TestDeprecatedFlagShorthandInDocs(t *testing.T) {
        f := NewFlagSet("bob", ContinueOnError)
        name := "noshorthandflag"
@@ -978,16 +1155,17 @@ const defaultOutput = `      --A                         for bootstrapping, allo
       --IP ip                     IP address with no default
       --IPMask ipMask             Netmask address with no default
       --IPNet ipNet               IP network with no default
-      --Ints intSlice             int slice with zero default
+      --Ints ints                 int slice with zero default
       --N int                     a non-zero int (default 27)
       --ND1 string[="bar"]        a string with NoOptDefVal (default "foo")
       --ND2 num[=4321]            a num with NoOptDefVal (default 1234)
       --StringArray stringArray   string array with zero default
-      --StringSlice stringSlice   string slice with zero default
+      --StringSlice strings       string slice with zero default
       --Z int                     an int that defaults to zero
       --custom custom             custom Value implementation
       --customP custom            a VarP with default (default 10)
       --maxT timeout              set timeout for dial
+  -v, --verbose count             verbosity
 `
 
 // Custom value that satisfies the Value interface.
@@ -1028,6 +1206,7 @@ func TestPrintDefaults(t *testing.T) {
        fs.ShorthandLookup("E").NoOptDefVal = "1234"
        fs.StringSlice("StringSlice", []string{}, "string slice with zero default")
        fs.StringArray("StringArray", []string{}, "string array with zero default")
+       fs.CountP("verbose", "v", "verbosity")
 
        var cv customValue
        fs.Var(&cv, "custom", "custom Value implementation")
index c4f47eb..d3dd72b 100644 (file)
@@ -98,4 +98,8 @@ func (f *FlagSet) AddGoFlagSet(newSet *goflag.FlagSet) {
        newSet.VisitAll(func(goflag *goflag.Flag) {
                f.AddGoFlag(goflag)
        })
+       if f.addedGoFlagSets == nil {
+               f.addedGoFlagSets = make([]*goflag.FlagSet, 0)
+       }
+       f.addedGoFlagSets = append(f.addedGoFlagSets, newSet)
 }
index 77e2d7d..5bd831b 100644 (file)
@@ -36,4 +36,12 @@ func TestGoflags(t *testing.T) {
        if getBool != true {
                t.Fatalf("expected getBool=true but got getBool=%v", getBool)
        }
+       if !f.Parsed() {
+               t.Fatal("f.Parsed() return false after f.Parse() called")
+       }
+
+       // in fact it is useless. because `go test` called flag.Parse()
+       if !goflag.CommandLine.Parsed() {
+               t.Fatal("goflag.CommandLine.Parsed() return false after f.Parse() called")
+       }
 }
diff --git a/vendor/github.com/spf13/pflag/int16.go b/vendor/github.com/spf13/pflag/int16.go
new file mode 100644 (file)
index 0000000..f1a01d0
--- /dev/null
@@ -0,0 +1,88 @@
+package pflag
+
+import "strconv"
+
+// -- int16 Value
+type int16Value int16
+
+func newInt16Value(val int16, p *int16) *int16Value {
+       *p = val
+       return (*int16Value)(p)
+}
+
+func (i *int16Value) Set(s string) error {
+       v, err := strconv.ParseInt(s, 0, 16)
+       *i = int16Value(v)
+       return err
+}
+
+func (i *int16Value) Type() string {
+       return "int16"
+}
+
+func (i *int16Value) String() string { return strconv.FormatInt(int64(*i), 10) }
+
+func int16Conv(sval string) (interface{}, error) {
+       v, err := strconv.ParseInt(sval, 0, 16)
+       if err != nil {
+               return 0, err
+       }
+       return int16(v), nil
+}
+
+// GetInt16 returns the int16 value of a flag with the given name
+func (f *FlagSet) GetInt16(name string) (int16, error) {
+       val, err := f.getFlagType(name, "int16", int16Conv)
+       if err != nil {
+               return 0, err
+       }
+       return val.(int16), nil
+}
+
+// Int16Var defines an int16 flag with specified name, default value, and usage string.
+// The argument p points to an int16 variable in which to store the value of the flag.
+func (f *FlagSet) Int16Var(p *int16, name string, value int16, usage string) {
+       f.VarP(newInt16Value(value, p), name, "", usage)
+}
+
+// Int16VarP is like Int16Var, but accepts a shorthand letter that can be used after a single dash.
+func (f *FlagSet) Int16VarP(p *int16, name, shorthand string, value int16, usage string) {
+       f.VarP(newInt16Value(value, p), name, shorthand, usage)
+}
+
+// Int16Var defines an int16 flag with specified name, default value, and usage string.
+// The argument p points to an int16 variable in which to store the value of the flag.
+func Int16Var(p *int16, name string, value int16, usage string) {
+       CommandLine.VarP(newInt16Value(value, p), name, "", usage)
+}
+
+// Int16VarP is like Int16Var, but accepts a shorthand letter that can be used after a single dash.
+func Int16VarP(p *int16, name, shorthand string, value int16, usage string) {
+       CommandLine.VarP(newInt16Value(value, p), name, shorthand, usage)
+}
+
+// Int16 defines an int16 flag with specified name, default value, and usage string.
+// The return value is the address of an int16 variable that stores the value of the flag.
+func (f *FlagSet) Int16(name string, value int16, usage string) *int16 {
+       p := new(int16)
+       f.Int16VarP(p, name, "", value, usage)
+       return p
+}
+
+// Int16P is like Int16, but accepts a shorthand letter that can be used after a single dash.
+func (f *FlagSet) Int16P(name, shorthand string, value int16, usage string) *int16 {
+       p := new(int16)
+       f.Int16VarP(p, name, shorthand, value, usage)
+       return p
+}
+
+// Int16 defines an int16 flag with specified name, default value, and usage string.
+// The return value is the address of an int16 variable that stores the value of the flag.
+func Int16(name string, value int16, usage string) *int16 {
+       return CommandLine.Int16P(name, "", value, usage)
+}
+
+// Int16P is like Int16, but accepts a shorthand letter that can be used after a single dash.
+func Int16P(name, shorthand string, value int16, usage string) *int16 {
+       return CommandLine.Int16P(name, shorthand, value, usage)
+}
diff --git a/vendor/github.com/spf13/pflag/printusage_test.go b/vendor/github.com/spf13/pflag/printusage_test.go
new file mode 100644 (file)
index 0000000..df982aa
--- /dev/null
@@ -0,0 +1,74 @@
+package pflag
+
+import (
+       "bytes"
+       "io"
+       "testing"
+)
+
+const expectedOutput = `      --long-form    Some description
+      --long-form2   Some description
+                       with multiline
+  -s, --long-name    Some description
+  -t, --long-name2   Some description with
+                       multiline
+`
+
+func setUpPFlagSet(buf io.Writer) *FlagSet {
+       f := NewFlagSet("test", ExitOnError)
+       f.Bool("long-form", false, "Some description")
+       f.Bool("long-form2", false, "Some description\n  with multiline")
+       f.BoolP("long-name", "s", false, "Some description")
+       f.BoolP("long-name2", "t", false, "Some description with\n  multiline")
+       f.SetOutput(buf)
+       return f
+}
+
+func TestPrintUsage(t *testing.T) {
+       buf := bytes.Buffer{}
+       f := setUpPFlagSet(&buf)
+       f.PrintDefaults()
+       res := buf.String()
+       if res != expectedOutput {
+               t.Errorf("Expected \n%s \nActual \n%s", expectedOutput, res)
+       }
+}
+
+func setUpPFlagSet2(buf io.Writer) *FlagSet {
+       f := NewFlagSet("test", ExitOnError)
+       f.Bool("long-form", false, "Some description")
+       f.Bool("long-form2", false, "Some description\n  with multiline")
+       f.BoolP("long-name", "s", false, "Some description")
+       f.BoolP("long-name2", "t", false, "Some description with\n  multiline")
+       f.StringP("some-very-long-arg", "l", "test", "Some very long description having break the limit")
+       f.StringP("other-very-long-arg", "o", "long-default-value", "Some very long description having break the limit")
+       f.String("some-very-long-arg2", "very long default value", "Some very long description\nwith line break\nmultiple")
+       f.SetOutput(buf)
+       return f
+}
+
+const expectedOutput2 = `      --long-form                    Some description
+      --long-form2                   Some description
+                                       with multiline
+  -s, --long-name                    Some description
+  -t, --long-name2                   Some description with
+                                       multiline
+  -o, --other-very-long-arg string   Some very long description having
+                                     break the limit (default
+                                     "long-default-value")
+  -l, --some-very-long-arg string    Some very long description having
+                                     break the limit (default "test")
+      --some-very-long-arg2 string   Some very long description
+                                     with line break
+                                     multiple (default "very long default
+                                     value")
+`
+
+func TestPrintUsage_2(t *testing.T) {
+       buf := bytes.Buffer{}
+       f := setUpPFlagSet2(&buf)
+       res := f.FlagUsagesWrapped(80)
+       if res != expectedOutput2 {
+               t.Errorf("Expected \n%q \nActual \n%q", expectedOutput2, res)
+       }
+}
index 276b7ed..fa7bc60 100644 (file)
@@ -52,7 +52,7 @@ func (f *FlagSet) GetStringArray(name string) ([]string, error) {
 
 // StringArrayVar defines a string flag with specified name, default value, and usage string.
 // The argument p points to a []string variable in which to store the values of the multiple flags.
-// The value of each argument will not try to be separated by comma
+// The value of each argument will not try to be separated by comma. Use a StringSlice for that.
 func (f *FlagSet) StringArrayVar(p *[]string, name string, value []string, usage string) {
        f.VarP(newStringArrayValue(value, p), name, "", usage)
 }
@@ -64,7 +64,7 @@ func (f *FlagSet) StringArrayVarP(p *[]string, name, shorthand string, value []s
 
 // StringArrayVar defines a string flag with specified name, default value, and usage string.
 // The argument p points to a []string variable in which to store the value of the flag.
-// The value of each argument will not try to be separated by comma
+// The value of each argument will not try to be separated by comma. Use a StringSlice for that.
 func StringArrayVar(p *[]string, name string, value []string, usage string) {
        CommandLine.VarP(newStringArrayValue(value, p), name, "", usage)
 }
@@ -76,7 +76,7 @@ func StringArrayVarP(p *[]string, name, shorthand string, value []string, usage
 
 // StringArray defines a string flag with specified name, default value, and usage string.
 // The return value is the address of a []string variable that stores the value of the flag.
-// The value of each argument will not try to be separated by comma
+// The value of each argument will not try to be separated by comma. Use a StringSlice for that.
 func (f *FlagSet) StringArray(name string, value []string, usage string) *[]string {
        p := []string{}
        f.StringArrayVarP(&p, name, "", value, usage)
@@ -92,7 +92,7 @@ func (f *FlagSet) StringArrayP(name, shorthand string, value []string, usage str
 
 // StringArray defines a string flag with specified name, default value, and usage string.
 // The return value is the address of a []string variable that stores the value of the flag.
-// The value of each argument will not try to be separated by comma
+// The value of each argument will not try to be separated by comma. Use a StringSlice for that.
 func StringArray(name string, value []string, usage string) *[]string {
        return CommandLine.StringArrayP(name, "", value, usage)
 }
index 05eee75..0cd3ccc 100644 (file)
@@ -82,6 +82,11 @@ func (f *FlagSet) GetStringSlice(name string) ([]string, error) {
 
 // StringSliceVar defines a string flag with specified name, default value, and usage string.
 // The argument p points to a []string variable in which to store the value of the flag.
+// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
+// For example:
+//   --ss="v1,v2" -ss="v3"
+// will result in
+//   []string{"v1", "v2", "v3"}
 func (f *FlagSet) StringSliceVar(p *[]string, name string, value []string, usage string) {
        f.VarP(newStringSliceValue(value, p), name, "", usage)
 }
@@ -93,6 +98,11 @@ func (f *FlagSet) StringSliceVarP(p *[]string, name, shorthand string, value []s
 
 // StringSliceVar defines a string flag with specified name, default value, and usage string.
 // The argument p points to a []string variable in which to store the value of the flag.
+// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
+// For example:
+//   --ss="v1,v2" -ss="v3"
+// will result in
+//   []string{"v1", "v2", "v3"}
 func StringSliceVar(p *[]string, name string, value []string, usage string) {
        CommandLine.VarP(newStringSliceValue(value, p), name, "", usage)
 }
@@ -104,6 +114,11 @@ func StringSliceVarP(p *[]string, name, shorthand string, value []string, usage
 
 // StringSlice defines a string flag with specified name, default value, and usage string.
 // The return value is the address of a []string variable that stores the value of the flag.
+// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
+// For example:
+//   --ss="v1,v2" -ss="v3"
+// will result in
+//   []string{"v1", "v2", "v3"}
 func (f *FlagSet) StringSlice(name string, value []string, usage string) *[]string {
        p := []string{}
        f.StringSliceVarP(&p, name, "", value, usage)
@@ -119,6 +134,11 @@ func (f *FlagSet) StringSliceP(name, shorthand string, value []string, usage str
 
 // StringSlice defines a string flag with specified name, default value, and usage string.
 // The return value is the address of a []string variable that stores the value of the flag.
+// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
+// For example:
+//   --ss="v1,v2" -ss="v3"
+// will result in
+//   []string{"v1", "v2", "v3"}
 func StringSlice(name string, value []string, usage string) *[]string {
        return CommandLine.StringSliceP(name, "", value, usage)
 }
diff --git a/vendor/github.com/spf13/pflag/string_to_int.go b/vendor/github.com/spf13/pflag/string_to_int.go
new file mode 100644 (file)
index 0000000..5ceda39
--- /dev/null
@@ -0,0 +1,149 @@
+package pflag
+
+import (
+       "bytes"
+       "fmt"
+       "strconv"
+       "strings"
+)
+
+// -- stringToInt Value
+type stringToIntValue struct {
+       value   *map[string]int
+       changed bool
+}
+
+func newStringToIntValue(val map[string]int, p *map[string]int) *stringToIntValue {
+       ssv := new(stringToIntValue)
+       ssv.value = p
+       *ssv.value = val
+       return ssv
+}
+
+// Format: a=1,b=2
+func (s *stringToIntValue) Set(val string) error {
+       ss := strings.Split(val, ",")
+       out := make(map[string]int, len(ss))
+       for _, pair := range ss {
+               kv := strings.SplitN(pair, "=", 2)
+               if len(kv) != 2 {
+                       return fmt.Errorf("%s must be formatted as key=value", pair)
+               }
+               var err error
+               out[kv[0]], err = strconv.Atoi(kv[1])
+               if err != nil {
+                       return err
+               }
+       }
+       if !s.changed {
+               *s.value = out
+       } else {
+               for k, v := range out {
+                       (*s.value)[k] = v
+               }
+       }
+       s.changed = true
+       return nil
+}
+
+func (s *stringToIntValue) Type() string {
+       return "stringToInt"
+}
+
+func (s *stringToIntValue) String() string {
+       var buf bytes.Buffer
+       i := 0
+       for k, v := range *s.value {
+               if i > 0 {
+                       buf.WriteRune(',')
+               }
+               buf.WriteString(k)
+               buf.WriteRune('=')
+               buf.WriteString(strconv.Itoa(v))
+               i++
+       }
+       return "[" + buf.String() + "]"
+}
+
+func stringToIntConv(val string) (interface{}, error) {
+       val = strings.Trim(val, "[]")
+       // An empty string would cause an empty map
+       if len(val) == 0 {
+               return map[string]int{}, nil
+       }
+       ss := strings.Split(val, ",")
+       out := make(map[string]int, len(ss))
+       for _, pair := range ss {
+               kv := strings.SplitN(pair, "=", 2)
+               if len(kv) != 2 {
+                       return nil, fmt.Errorf("%s must be formatted as key=value", pair)
+               }
+               var err error
+               out[kv[0]], err = strconv.Atoi(kv[1])
+               if err != nil {
+                       return nil, err
+               }
+       }
+       return out, nil
+}
+
+// GetStringToInt return the map[string]int value of a flag with the given name
+func (f *FlagSet) GetStringToInt(name string) (map[string]int, error) {
+       val, err := f.getFlagType(name, "stringToInt", stringToIntConv)
+       if err != nil {
+               return map[string]int{}, err
+       }
+       return val.(map[string]int), nil
+}
+
+// StringToIntVar defines a string flag with specified name, default value, and usage string.
+// The argument p points to a map[string]int variable in which to store the values of the multiple flags.
+// The value of each argument will not try to be separated by comma
+func (f *FlagSet) StringToIntVar(p *map[string]int, name string, value map[string]int, usage string) {
+       f.VarP(newStringToIntValue(value, p), name, "", usage)
+}
+
+// StringToIntVarP is like StringToIntVar, but accepts a shorthand letter that can be used after a single dash.
+func (f *FlagSet) StringToIntVarP(p *map[string]int, name, shorthand string, value map[string]int, usage string) {
+       f.VarP(newStringToIntValue(value, p), name, shorthand, usage)
+}
+
+// StringToIntVar defines a string flag with specified name, default value, and usage string.
+// The argument p points to a map[string]int variable in which to store the value of the flag.
+// The value of each argument will not try to be separated by comma
+func StringToIntVar(p *map[string]int, name string, value map[string]int, usage string) {
+       CommandLine.VarP(newStringToIntValue(value, p), name, "", usage)
+}
+
+// StringToIntVarP is like StringToIntVar, but accepts a shorthand letter that can be used after a single dash.
+func StringToIntVarP(p *map[string]int, name, shorthand string, value map[string]int, usage string) {
+       CommandLine.VarP(newStringToIntValue(value, p), name, shorthand, usage)
+}
+
+// StringToInt defines a string flag with specified name, default value, and usage string.
+// The return value is the address of a map[string]int variable that stores the value of the flag.
+// The value of each argument will not try to be separated by comma
+func (f *FlagSet) StringToInt(name string, value map[string]int, usage string) *map[string]int {
+       p := map[string]int{}
+       f.StringToIntVarP(&p, name, "", value, usage)
+       return &p
+}
+
+// StringToIntP is like StringToInt, but accepts a shorthand letter that can be used after a single dash.
+func (f *FlagSet) StringToIntP(name, shorthand string, value map[string]int, usage string) *map[string]int {
+       p := map[string]int{}
+       f.StringToIntVarP(&p, name, shorthand, value, usage)
+ &