1
0
mirror of https://github.com/Luzifer/mondash.git synced 2024-09-19 17:02:58 +00:00

Update vendors

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2019-05-25 13:27:00 +02:00
parent b2d529faf8
commit d8a503015e
Signed by: luzifer
GPG Key ID: DC2729FDD34BE99E
754 changed files with 100838 additions and 162474 deletions

View File

@ -2,62 +2,93 @@
[[projects]]
name = "github.com/Luzifer/rconfig"
packages = ["."]
revision = "7aef1d393c1e2d0758901853b59981c7adc67c7e"
version = "v1.2.0"
digest = "1:1ba36a065708f92dece8a65c26e8ba3c09229104e1b0ea8327c6502755cb1ed4"
name = "github.com/Luzifer/mondash"
packages = ["client"]
pruneopts = "NUT"
revision = "54340f1540f9a9e56122cc0e5f2ffc57fa59526c"
version = "v2.0.1"
[[projects]]
digest = "1:f6cc072a289a686fda22819d871cd1b0407640141b2f6616dfbab957c96bf6c3"
name = "github.com/Luzifer/rconfig"
packages = ["."]
pruneopts = "NUT"
revision = "d38bbb45e0ac240398f2af710a6b21833954f006"
version = "v2.2.1"
[[projects]]
digest = "1:974a3957354622ffb7b0b345374322f782800a8e4c4517022fce77ef796249c5"
name = "github.com/gosimple/slug"
packages = ["."]
revision = "e9f42fa127660e552d0ad2b589868d403a9be7c6"
version = "v1.1.1"
pruneopts = "NUT"
revision = "e78d39397ba41d2c33d9615044ef93eca8a309d8"
version = "v1.5.0"
[[projects]]
digest = "1:58999a98719fddbac6303cb17e8d85b945f60b72f48e3a2df6b950b97fa926f1"
name = "github.com/konsorten/go-windows-terminal-sequences"
packages = ["."]
pruneopts = "NUT"
revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e"
version = "v1.0.2"
[[projects]]
branch = "master"
digest = "1:16e2136a67ec44aa2d1d6b0fd65394b3c4a8b2a1b6730c77967f7b7b06b179b2"
name = "github.com/rainycape/unidecode"
packages = ["."]
pruneopts = "NUT"
revision = "cb7f23ec59bec0d61b19c56cd88cee3d0cc1870c"
[[projects]]
digest = "1:f4aaa07a6c33f2b354726d0571acbc8ca118837c75709f6353203ae1a3f8eeab"
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "d682213848ed68c0a260ca37d6dd5ace8423f5ba"
version = "v1.0.4"
pruneopts = "NUT"
revision = "839c75faf7f98a33d445d181f3018b5c3409a45e"
version = "v1.4.2"
[[projects]]
digest = "1:9d8420bbf131d1618bde6530af37c3799340d3762cc47210c1d9532a4c3a2779"
name = "github.com/spf13/pflag"
packages = ["."]
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
revision = "a6600008915114d9c087fad9f03d75087b1a74df"
pruneopts = "NUT"
revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
version = "v1.0.3"
[[projects]]
branch = "master"
digest = "1:ad23a6be2878ef5a2a1f37e0bd4461a9239ce205ef32192bb07bfe5cfb8dbcf1"
name = "golang.org/x/sys"
packages = ["unix","windows"]
revision = "2c42eef0765b9837fbdab12011af7830f55f88f0"
packages = ["unix"]
pruneopts = "NUT"
revision = "dbbf3f1254d491605cf4a0034ce25d0dc71b0c58"
[[projects]]
branch = "v2"
digest = "1:1ab6db2d2bd353449c5d1e976ba7a92a0ece6e83aaab3e6674f8f2f1faebb85a"
name = "gopkg.in/validator.v2"
packages = ["."]
revision = "460c83432a98c35224a6fe352acf8b23e067ad06"
pruneopts = "NUT"
revision = "135c24b11c19e52befcae2ec3fca5d9b78c4e98e"
[[projects]]
branch = "v2"
digest = "1:18108594151654e9e696b27b181b953f9a90b16bf14d253dd1b397b025a1487f"
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4"
pruneopts = "NUT"
revision = "51d6538a90f86fe93ac480b35f37b2be17fef232"
version = "v2.2.2"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "89d89fccca850baa0f4fb427e21c16a158365a1470a04a0b6759c24b1cf4742b"
input-imports = [
"github.com/Luzifer/mondash/client",
"github.com/Luzifer/rconfig",
"github.com/gosimple/slug",
"github.com/sirupsen/logrus",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,6 +1,6 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
@ -18,17 +18,30 @@
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
ignored = ["github.com/Luzifer/mondash"]
[[constraint]]
name = "github.com/Luzifer/mondash"
version = "2.0.1"
[[constraint]]
name = "github.com/Luzifer/rconfig"
version = "1.2.0"
version = "2.2.1"
[[constraint]]
name = "github.com/gosimple/slug"
version = "1.1.1"
version = "1.5.0"
[[constraint]]
name = "github.com/sirupsen/logrus"
version = "1.0.4"
version = "1.4.2"
[prune]
non-go = true
go-tests = true
unused-packages = true

View File

@ -1,8 +0,0 @@
language: go
go:
- 1.6
- 1.7
- tip
script: go test -v -race -cover ./...

View File

@ -1,9 +0,0 @@
# 1.2.0 / 2017-06-19
* Add ParseAndValidate method
# 1.1.0 / 2016-06-28
* Support time.Duration config parameters
* Added goreportcard badge
* Added testcase for using bool with ENV and default

View File

@ -1,13 +1,202 @@
Copyright 2015 Knut Ahlers <knut@ahlers.me>
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015- Knut Ahlers <knut@ahlers.me>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,87 +0,0 @@
[![Build Status](https://travis-ci.org/Luzifer/rconfig.svg?branch=master)](https://travis-ci.org/Luzifer/rconfig)
[![License: Apache v2.0](https://badge.luzifer.io/v1/badge?color=5d79b5&title=license&text=Apache+v2.0)](http://www.apache.org/licenses/LICENSE-2.0)
[![Documentation](https://badge.luzifer.io/v1/badge?title=godoc&text=reference)](https://godoc.org/github.com/Luzifer/rconfig)
[![Go Report](http://goreportcard.com/badge/Luzifer/rconfig)](http://goreportcard.com/report/Luzifer/rconfig)
## Description
> Package rconfig implements a CLI configuration reader with struct-embedded defaults, environment variables and posix compatible flag parsing using the [pflag](https://github.com/spf13/pflag) library.
## Installation
Install by running:
```
go get -u github.com/Luzifer/rconfig
```
OR fetch a specific version:
```
go get -u gopkg.in/luzifer/rconfig.v1
```
Run tests by running:
```
go test -v -race -cover github.com/Luzifer/rconfig
```
## Usage
A very simple usecase is to just configure a struct inside the vars section of your `main.go` and to parse the commandline flags from the `main()` function:
```go
package main
import (
"fmt"
"github.com/Luzifer/rconfig"
)
var (
cfg = struct {
Username string `default:"unknown" flag:"user" description:"Your name"`
Details struct {
Age int `default:"25" flag:"age" env:"age" description:"Your age"`
}
}{}
)
func main() {
rconfig.Parse(&cfg)
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
cfg.Username,
cfg.Details.Age)
}
```
### Provide variable defaults by using a file
Given you have a file `~/.myapp.yml` containing some secrets or usernames (for the example below username is assumed to be "luzifer") as a default configuration for your application you can use this source code to load the defaults from that file using the `vardefault` tag in your configuration struct.
The order of the directives (lower number = higher precedence):
1. Flags provided in command line
1. Environment variables
1. Variable defaults (`vardefault` tag in the struct)
1. `default` tag in the struct
```go
var cfg = struct {
Username string `vardefault:"username" flag:"username" description:"Your username"`
}
func main() {
rconfig.SetVariableDefaults(rconfig.VarDefaultsFromYAMLFile("~/.myapp.yml"))
rconfig.Parse(&cfg)
fmt.Printf("Username = %s", cfg.Username)
// Output: Username = luzifer
}
```
## More info
You can see the full reference documentation of the rconfig package [at godoc.org](https://godoc.org/github.com/Luzifer/rconfig), or through go's standard documentation system by running `godoc -http=:6060` and browsing to [http://localhost:6060/pkg/github.com/Luzifer/rconfig](http://localhost:6060/pkg/github.com/Luzifer/rconfig) after installation.

View File

@ -0,0 +1,64 @@
package rconfig
import "strings"
type characterClass [2]rune
func (c characterClass) Contains(r rune) bool {
return c[0] <= r && c[1] >= r
}
type characterClasses []characterClass
func (c characterClasses) Contains(r rune) bool {
for _, cc := range c {
if cc.Contains(r) {
return true
}
}
return false
}
var (
charGroupUpperLetter = characterClass{'A', 'Z'}
charGroupLowerLetter = characterClass{'a', 'z'}
charGroupNumber = characterClass{'0', '9'}
charGroupLowerNumber = characterClasses{charGroupLowerLetter, charGroupNumber}
)
func deriveEnvVarName(s string) string {
var (
words []string
word []rune
)
for _, l := range s {
switch {
case charGroupUpperLetter.Contains(l):
if len(word) > 0 && charGroupLowerNumber.Contains(word[len(word)-1]) {
words = append(words, string(word))
word = []rune{}
}
word = append(word, l)
case charGroupLowerLetter.Contains(l):
if len(word) > 1 && charGroupUpperLetter.Contains(word[len(word)-1]) {
words = append(words, string(word[0:len(word)-1]))
word = word[len(word)-1:]
}
word = append(word, l)
case charGroupNumber.Contains(l):
word = append(word, l)
default:
if len(word) > 0 {
words = append(words, string(word))
}
word = []rune{}
}
}
words = append(words, string(word))
return strings.ToUpper(strings.Join(words, "_"))
}

View File

@ -1,70 +0,0 @@
package rconfig
import (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing bool parsing", func() {
type t struct {
Test1 bool `default:"true"`
Test2 bool `default:"false" flag:"test2"`
Test3 bool `default:"true" flag:"test3,t"`
Test4 bool `flag:"test4"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--test2",
"-t",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test1).To(Equal(true))
Expect(cfg.Test2).To(Equal(true))
Expect(cfg.Test3).To(Equal(true))
Expect(cfg.Test4).To(Equal(false))
})
})
var _ = Describe("Testing to set bool from ENV with default", func() {
type t struct {
Test1 bool `default:"true" env:"TEST1"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{}
})
JustBeforeEach(func() {
os.Unsetenv("TEST1")
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test1).To(Equal(true))
})
})

View File

@ -16,9 +16,25 @@ import (
validator "gopkg.in/validator.v2"
)
type afterFunc func() error
var (
autoEnv bool
fs *pflag.FlagSet
variableDefaults map[string]string
timeParserFormats = []string{
// Default constants
time.RFC3339Nano, time.RFC3339,
time.RFC1123Z, time.RFC1123,
time.RFC822Z, time.RFC822,
time.RFC850, time.RubyDate, time.UnixDate, time.ANSIC,
"2006-01-02 15:04:05.999999999 -0700 MST",
// More uncommon time formats
"2006-01-02 15:04:05", "2006-01-02 15:04:05Z07:00", // Simplified ISO time format
"01/02/2006 15:04:05", "01/02/2006 15:04:05Z07:00", // US time format
"02.01.2006 15:04:05", "02.01.2006 15:04:05Z07:00", // DE time format
}
)
func init() {
@ -60,6 +76,18 @@ func Args() []string {
return fs.Args()
}
// AddTimeParserFormats adds custom formats to parse time.Time fields
func AddTimeParserFormats(f ...string) {
timeParserFormats = append(timeParserFormats, f...)
}
// AutoEnv enables or disables automated env variable guessing. If no `env` struct
// tag was set and AutoEnv is enabled the env variable name is derived from the
// name of the field: `MyFieldName` will get `MY_FIELD_NAME`
func AutoEnv(enable bool) {
autoEnv = enable
}
// Usage prints a basic usage with the corresponding defaults for the flags to
// os.Stdout. The defaults are derived from the `default` struct-tag and the ENV.
func Usage() {
@ -89,22 +117,37 @@ func parse(in interface{}, args []string) error {
}
fs = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
if err := execTags(in, fs); err != nil {
afterFuncs, err := execTags(in, fs)
if err != nil {
return err
}
return fs.Parse(args)
if err := fs.Parse(args); err != nil {
return err
}
if afterFuncs != nil {
for _, f := range afterFuncs {
if err := f(); err != nil {
return err
}
}
}
return nil
}
func execTags(in interface{}, fs *pflag.FlagSet) error {
func execTags(in interface{}, fs *pflag.FlagSet) ([]afterFunc, error) {
if reflect.TypeOf(in).Kind() != reflect.Ptr {
return errors.New("Calling parser with non-pointer")
return nil, errors.New("Calling parser with non-pointer")
}
if reflect.ValueOf(in).Elem().Kind() != reflect.Struct {
return errors.New("Calling parser with pointer to non-struct")
return nil, errors.New("Calling parser with pointer to non-struct")
}
afterFuncs := []afterFunc{}
st := reflect.ValueOf(in).Elem()
for i := 0; i < st.NumField(); i++ {
valField := st.Field(i)
@ -116,7 +159,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
}
value := varDefault(typeField.Tag.Get("vardefault"), typeField.Tag.Get("default"))
value = envDefault(typeField.Tag.Get("env"), value)
value = envDefault(typeField, value)
parts := strings.Split(typeField.Tag.Get("flag"), ",")
switch typeField.Type {
@ -126,7 +169,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if value == "" {
v = time.Duration(0)
} else {
return err
return nil, err
}
}
@ -140,6 +183,53 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
valField.Set(reflect.ValueOf(v))
}
continue
case reflect.TypeOf(time.Time{}):
var sVar string
if typeField.Tag.Get("flag") != "" {
if len(parts) == 1 {
fs.StringVar(&sVar, parts[0], value, typeField.Tag.Get("description"))
} else {
fs.StringVarP(&sVar, parts[0], parts[1], value, typeField.Tag.Get("description"))
}
} else {
sVar = value
}
afterFuncs = append(afterFuncs, func(valField reflect.Value, sVar *string) func() error {
return func() error {
if *sVar == "" {
// No time, no problem
return nil
}
// Check whether we could have a timestamp
if ts, err := strconv.ParseInt(*sVar, 10, 64); err == nil {
t := time.Unix(ts, 0)
valField.Set(reflect.ValueOf(t))
return nil
}
// We haven't so lets walk through possible time formats
matched := false
for _, tf := range timeParserFormats {
if t, err := time.Parse(tf, *sVar); err == nil {
matched = true
valField.Set(reflect.ValueOf(t))
return nil
}
}
if !matched {
return fmt.Errorf("Value %q did not match expected time formats", *sVar)
}
return nil
}
}(valField, &sVar))
continue
}
switch typeField.Type.Kind() {
@ -172,7 +262,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if value == "" {
vt = 0
} else {
return err
return nil, err
}
}
if typeField.Tag.Get("flag") != "" {
@ -187,7 +277,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if value == "" {
vt = 0
} else {
return err
return nil, err
}
}
if typeField.Tag.Get("flag") != "" {
@ -202,7 +292,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if value == "" {
vt = 0.0
} else {
return err
return nil, err
}
}
if typeField.Tag.Get("flag") != "" {
@ -212,9 +302,11 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
}
case reflect.Struct:
if err := execTags(valField.Addr().Interface(), fs); err != nil {
return err
afs, err := execTags(valField.Addr().Interface(), fs)
if err != nil {
return nil, err
}
afterFuncs = append(afterFuncs, afs...)
case reflect.Slice:
switch typeField.Type.Elem().Kind() {
@ -223,7 +315,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
for _, v := range strings.Split(value, ",") {
it, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
if err != nil {
return err
return nil, err
}
def = append(def, int(it))
}
@ -237,7 +329,10 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if len(del) == 0 {
del = ","
}
def := strings.Split(value, del)
var def = []string{}
if value != "" {
def = strings.Split(value, del)
}
if len(parts) == 1 {
fs.StringSliceVar(valField.Addr().Interface().(*[]string), parts[0], def, typeField.Tag.Get("description"))
} else {
@ -247,7 +342,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
}
}
return nil
return afterFuncs, nil
}
func registerFlagFloat(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt float64, desc string) {
@ -331,9 +426,14 @@ func registerFlagUint(t reflect.Kind, fs *pflag.FlagSet, field interface{}, part
}
}
func envDefault(env, def string) string {
func envDefault(field reflect.StructField, def string) string {
value := def
env := field.Tag.Get("env")
if env == "" && autoEnv {
env = deriveEnvVarName(field.Name)
}
if env != "" {
if e := os.Getenv(env); e != "" {
value = e

View File

@ -1,41 +0,0 @@
package rconfig
import (
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Duration", func() {
type t struct {
Test time.Duration `flag:"duration"`
TestS time.Duration `flag:"other-duration,o"`
TestDef time.Duration `default:"30h"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--duration=23s", "-o", "45m",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test).To(Equal(23 * time.Second))
Expect(cfg.TestS).To(Equal(45 * time.Minute))
Expect(cfg.TestDef).To(Equal(30 * time.Hour))
})
})

View File

@ -1,56 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing errors", func() {
It("should not accept string as int", func() {
Expect(parse(&struct {
A int `default:"a"`
}{}, []string{})).To(HaveOccurred())
})
It("should not accept string as float", func() {
Expect(parse(&struct {
A float32 `default:"a"`
}{}, []string{})).To(HaveOccurred())
})
It("should not accept string as uint", func() {
Expect(parse(&struct {
A uint `default:"a"`
}{}, []string{})).To(HaveOccurred())
})
It("should not accept string as uint in sub-struct", func() {
Expect(parse(&struct {
B struct {
A uint `default:"a"`
}
}{}, []string{})).To(HaveOccurred())
})
It("should not accept string slice as int slice", func() {
Expect(parse(&struct {
A []int `default:"a,bn"`
}{}, []string{})).To(HaveOccurred())
})
It("should not accept variables not being pointers", func() {
cfg := struct {
A string `default:"a"`
}{}
Expect(parse(cfg, []string{})).To(HaveOccurred())
})
It("should not accept variables not being pointers to structs", func() {
cfg := "test"
Expect(parse(cfg, []string{})).To(HaveOccurred())
})
})

View File

@ -1,37 +0,0 @@
package rconfig
import (
"fmt"
"os"
)
func ExampleParse() {
// We're building an example configuration with a sub-struct to be filled
// by the Parse command.
config := struct {
Username string `default:"unknown" flag:"user,u" description:"Your name"`
Details struct {
Age int `default:"25" flag:"age" description:"Your age"`
}
}{}
// To have more relieable results we're setting os.Args to a known value.
// In real-life use cases you wouldn't do this but parse the original
// commandline arguments.
os.Args = []string{
"example",
"--user=Luzifer",
}
Parse(&config)
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
config.Username,
config.Details.Age)
// You can also show an usage message for your user
Usage()
// Output:
// Hello Luzifer, happy birthday for your 25th birthday.
}

View File

@ -1,44 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing float parsing", func() {
type t struct {
Test32 float32 `flag:"float32"`
Test32P float32 `flag:"float32p,3"`
Test64 float64 `flag:"float64"`
Test64P float64 `flag:"float64p,6"`
TestDef float32 `default:"66.256"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--float32=5.5", "-3", "6.6",
"--float64=7.7", "-6", "8.8",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test32).To(Equal(float32(5.5)))
Expect(cfg.Test32P).To(Equal(float32(6.6)))
Expect(cfg.Test64).To(Equal(float64(7.7)))
Expect(cfg.Test64P).To(Equal(float64(8.8)))
Expect(cfg.TestDef).To(Equal(float32(66.256)))
})
})

View File

@ -1,128 +0,0 @@
package rconfig
import (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing general parsing", func() {
type t struct {
Test string `default:"foo" env:"shell" flag:"shell" description:"Test"`
Test2 string `default:"blub" env:"testvar" flag:"testvar,t" description:"Test"`
DefaultFlag string `default:"goo"`
SadFlag string
}
type tValidated struct {
Test string `flag:"test" default:"" validate:"nonzero"`
}
var (
err error
args []string
cfg t
)
Context("with defined arguments", func() {
BeforeEach(func() {
cfg = t{}
args = []string{
"--shell=test23",
"-t", "bla",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have parsed the expected values", func() {
Expect(cfg.Test).To(Equal("test23"))
Expect(cfg.Test2).To(Equal("bla"))
Expect(cfg.SadFlag).To(Equal(""))
Expect(cfg.DefaultFlag).To(Equal("goo"))
})
})
Context("with no arguments", func() {
BeforeEach(func() {
cfg = t{}
args = []string{}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the default value", func() {
Expect(cfg.Test).To(Equal("foo"))
})
})
Context("with no arguments and set env", func() {
BeforeEach(func() {
cfg = t{}
args = []string{}
os.Setenv("shell", "test546")
})
AfterEach(func() {
os.Unsetenv("shell")
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the value from env", func() {
Expect(cfg.Test).To(Equal("test546"))
})
})
Context("with additional arguments", func() {
BeforeEach(func() {
cfg = t{}
args = []string{
"--shell=test23",
"-t", "bla",
"positional1", "positional2",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have parsed the expected values", func() {
Expect(cfg.Test).To(Equal("test23"))
Expect(cfg.Test2).To(Equal("bla"))
Expect(cfg.SadFlag).To(Equal(""))
Expect(cfg.DefaultFlag).To(Equal("goo"))
})
It("should have detected the positional arguments", func() {
Expect(Args()).To(Equal([]string{"positional1", "positional2"}))
})
})
Context("making use of the validator package", func() {
var cfgValidated tValidated
BeforeEach(func() {
cfgValidated = tValidated{}
args = []string{}
})
JustBeforeEach(func() {
err = parseAndValidate(&cfgValidated, args)
})
It("should have errored", func() { Expect(err).To(HaveOccurred()) })
})
})

View File

@ -1,54 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing int parsing", func() {
type t struct {
Test int `flag:"int"`
TestP int `flag:"intp,i"`
Test8 int8 `flag:"int8"`
Test8P int8 `flag:"int8p,8"`
Test32 int32 `flag:"int32"`
Test32P int32 `flag:"int32p,3"`
Test64 int64 `flag:"int64"`
Test64P int64 `flag:"int64p,6"`
TestDef int8 `default:"66"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--int=1", "-i", "2",
"--int8=3", "-8", "4",
"--int32=5", "-3", "6",
"--int64=7", "-6", "8",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test).To(Equal(1))
Expect(cfg.TestP).To(Equal(2))
Expect(cfg.Test8).To(Equal(int8(3)))
Expect(cfg.Test8P).To(Equal(int8(4)))
Expect(cfg.Test32).To(Equal(int32(5)))
Expect(cfg.Test32P).To(Equal(int32(6)))
Expect(cfg.Test64).To(Equal(int64(7)))
Expect(cfg.Test64P).To(Equal(int64(8)))
Expect(cfg.TestDef).To(Equal(int8(66)))
})
})

View File

@ -1,40 +0,0 @@
package rconfig_test
import (
"os"
. "github.com/Luzifer/rconfig"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing os.Args", func() {
type t struct {
A string `default:"a" flag:"a"`
}
var (
err error
cfg t
)
JustBeforeEach(func() {
err = Parse(&cfg)
})
Context("With only valid arguments", func() {
BeforeEach(func() {
cfg = t{}
os.Args = []string{"--a=bar"}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.A).To(Equal("bar"))
})
})
})

View File

@ -1,87 +0,0 @@
package rconfig
import (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Precedence", func() {
type t struct {
A int `default:"1" vardefault:"a" env:"a" flag:"avar,a" description:"a"`
}
var (
err error
cfg t
args []string
vardefaults map[string]string
)
JustBeforeEach(func() {
cfg = t{}
SetVariableDefaults(vardefaults)
err = parse(&cfg, args)
})
Context("Provided: Flag, Env, Default, VarDefault", func() {
BeforeEach(func() {
args = []string{"-a", "5"}
os.Setenv("a", "8")
vardefaults = map[string]string{
"a": "3",
}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the flag value", func() {
Expect(cfg.A).To(Equal(5))
})
})
Context("Provided: Env, Default, VarDefault", func() {
BeforeEach(func() {
args = []string{}
os.Setenv("a", "8")
vardefaults = map[string]string{
"a": "3",
}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the env value", func() {
Expect(cfg.A).To(Equal(8))
})
})
Context("Provided: Default, VarDefault", func() {
BeforeEach(func() {
args = []string{}
os.Unsetenv("a")
vardefaults = map[string]string{
"a": "3",
}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the vardefault value", func() {
Expect(cfg.A).To(Equal(3))
})
})
Context("Provided: Default", func() {
BeforeEach(func() {
args = []string{}
os.Unsetenv("a")
vardefaults = map[string]string{}
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have used the default value", func() {
Expect(cfg.A).To(Equal(1))
})
})
})

View File

@ -1,13 +0,0 @@
package rconfig_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestRconfig(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Rconfig Suite")
}

View File

@ -1,51 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing slices", func() {
type t struct {
Int []int `default:"1,2,3" flag:"int"`
String []string `default:"a,b,c" flag:"string"`
IntP []int `default:"1,2,3" flag:"intp,i"`
StringP []string `default:"a,b,c" flag:"stringp,s"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--int=4,5", "-s", "hallo,welt",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values for int-slice", func() {
Expect(len(cfg.Int)).To(Equal(2))
Expect(cfg.Int).To(Equal([]int{4, 5}))
Expect(cfg.Int).NotTo(Equal([]int{5, 4}))
})
It("should have the expected values for int-shorthand-slice", func() {
Expect(len(cfg.IntP)).To(Equal(3))
Expect(cfg.IntP).To(Equal([]int{1, 2, 3}))
})
It("should have the expected values for string-slice", func() {
Expect(len(cfg.String)).To(Equal(3))
Expect(cfg.String).To(Equal([]string{"a", "b", "c"}))
})
It("should have the expected values for string-shorthand-slice", func() {
Expect(len(cfg.StringP)).To(Equal(2))
Expect(cfg.StringP).To(Equal([]string{"hallo", "welt"}))
})
})

View File

@ -1,36 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing sub-structs", func() {
type t struct {
Test string `default:"blubb"`
Sub struct {
Test string `default:"Hallo"`
}
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test).To(Equal("blubb"))
Expect(cfg.Sub.Test).To(Equal("Hallo"))
})
})

View File

@ -1,59 +0,0 @@
package rconfig
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing uint parsing", func() {
type t struct {
Test uint `flag:"int"`
TestP uint `flag:"intp,i"`
Test8 uint8 `flag:"int8"`
Test8P uint8 `flag:"int8p,8"`
Test16 uint16 `flag:"int16"`
Test16P uint16 `flag:"int16p,1"`
Test32 uint32 `flag:"int32"`
Test32P uint32 `flag:"int32p,3"`
Test64 uint64 `flag:"int64"`
Test64P uint64 `flag:"int64p,6"`
TestDef uint8 `default:"66"`
}
var (
err error
args []string
cfg t
)
BeforeEach(func() {
cfg = t{}
args = []string{
"--int=1", "-i", "2",
"--int8=3", "-8", "4",
"--int32=5", "-3", "6",
"--int64=7", "-6", "8",
"--int16=9", "-1", "10",
}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.Test).To(Equal(uint(1)))
Expect(cfg.TestP).To(Equal(uint(2)))
Expect(cfg.Test8).To(Equal(uint8(3)))
Expect(cfg.Test8P).To(Equal(uint8(4)))
Expect(cfg.Test32).To(Equal(uint32(5)))
Expect(cfg.Test32P).To(Equal(uint32(6)))
Expect(cfg.Test64).To(Equal(uint64(7)))
Expect(cfg.Test64P).To(Equal(uint64(8)))
Expect(cfg.Test16).To(Equal(uint16(9)))
Expect(cfg.Test16P).To(Equal(uint16(10)))
Expect(cfg.TestDef).To(Equal(uint8(66)))
})
})

View File

@ -1,122 +0,0 @@
package rconfig
import (
"io/ioutil"
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Testing variable defaults", func() {
type t struct {
MySecretValue string `default:"secret" env:"foo" vardefault:"my_secret_value"`
MyUsername string `default:"luzifer" vardefault:"username"`
SomeVar string `flag:"var" description:"some variable"`
IntVar int64 `vardefault:"int_var" default:"23"`
}
var (
err error
cfg t
args = []string{}
vardefaults = map[string]string{
"my_secret_value": "veryverysecretkey",
"unkownkey": "hi there",
"int_var": "42",
}
)
BeforeEach(func() {
cfg = t{}
})
JustBeforeEach(func() {
err = parse(&cfg, args)
})
Context("With manually provided variables", func() {
BeforeEach(func() {
SetVariableDefaults(vardefaults)
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(42)))
Expect(cfg.MySecretValue).To(Equal("veryverysecretkey"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
Context("With defaults from YAML data", func() {
BeforeEach(func() {
yamlData := []byte("---\nmy_secret_value: veryverysecretkey\nunknownkey: hi there\nint_var: 42\n")
SetVariableDefaults(VarDefaultsFromYAML(yamlData))
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(42)))
Expect(cfg.MySecretValue).To(Equal("veryverysecretkey"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
Context("With defaults from YAML file", func() {
var tmp *os.File
BeforeEach(func() {
tmp, _ = ioutil.TempFile("", "")
yamlData := "---\nmy_secret_value: veryverysecretkey\nunknownkey: hi there\nint_var: 42\n"
tmp.WriteString(yamlData)
SetVariableDefaults(VarDefaultsFromYAMLFile(tmp.Name()))
})
AfterEach(func() {
tmp.Close()
os.Remove(tmp.Name())
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(42)))
Expect(cfg.MySecretValue).To(Equal("veryverysecretkey"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
Context("With defaults from invalid YAML data", func() {
BeforeEach(func() {
yamlData := []byte("---\nmy_secret_value = veryverysecretkey\nunknownkey = hi there\nint_var = 42\n")
SetVariableDefaults(VarDefaultsFromYAML(yamlData))
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(23)))
Expect(cfg.MySecretValue).To(Equal("secret"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
Context("With defaults from non existent YAML file", func() {
BeforeEach(func() {
file := "/tmp/this_file_should_not_exist_146e26723r"
SetVariableDefaults(VarDefaultsFromYAMLFile(file))
})
It("should not have errored", func() { Expect(err).NotTo(HaveOccurred()) })
It("should have the expected values", func() {
Expect(cfg.IntVar).To(Equal(int64(23)))
Expect(cfg.MySecretValue).To(Equal("secret"))
Expect(cfg.MyUsername).To(Equal("luzifer"))
Expect(cfg.SomeVar).To(Equal(""))
})
})
})

View File

@ -1,2 +0,0 @@
_*
cover*.out

View File

@ -1,54 +0,0 @@
slug
====
Package `slug` generate slug from unicode string, URL-friendly slugify with
multiple languages support.
[![GoDoc](https://godoc.org/github.com/gosimple/slug?status.png)](https://godoc.org/github.com/gosimple/slug)
[![Build Status](https://drone.io/github.com/gosimple/slug/status.png)](https://drone.io/github.com/gosimple/slug/latest)
[Documentation online](http://godoc.org/github.com/gosimple/slug)
## Example
package main
import(
"github.com/gosimple/slug"
"fmt"
)
func main () {
text := slug.Make("Hellö Wörld хелло ворлд")
fmt.Println(text) // Will print: "hello-world-khello-vorld"
someText := slug.Make("影師")
fmt.Println(someText) // Will print: "ying-shi"
enText := slug.MakeLang("This & that", "en")
fmt.Println(enText) // Will print: "this-and-that"
deText := slug.MakeLang("Diese & Dass", "de")
fmt.Println(deText) // Will print: "diese-und-dass"
slug.CustomSub = map[string]string{
"water": "sand",
}
textSub := slug.Make("water is hot")
fmt.Println(textSub) // Will print: "sand-is-hot"
}
### Requests or bugs?
<https://github.com/gosimple/slug/issues>
## Installation
go get -u github.com/gosimple/slug
## License
The source files are distributed under the
[Mozilla Public License, version 2.0](http://mozilla.org/MPL/2.0/),
unless otherwise noted.
Please read the [FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html)
if you have further questions regarding the license.

View File

@ -7,7 +7,9 @@ package slug
func init() {
// Merge language subs with the default one
for _, sub := range []*map[rune]string{&deSub, &enSub, &plSub, &esSub} {
for _, sub := range []*map[rune]string{
&deSub, &enSub, &esSub, &fiSub, &grSub, &nlSub, &plSub,
} {
for key, value := range defaultSub {
(*sub)[key] = value
}
@ -27,6 +29,12 @@ var defaultSub = map[rune]string{
var deSub = map[rune]string{
'&': "und",
'@': "an",
'ä': "ae",
'Ä': "ae",
'ö': "oe",
'Ö': "oe",
'ü': "ue",
'Ü': "ue",
}
var enSub = map[rune]string{
@ -34,16 +42,16 @@ var enSub = map[rune]string{
'@': "at",
}
var plSub = map[rune]string{
'&': "i",
'@': "na",
}
var esSub = map[rune]string{
'&': "y",
'@': "en",
}
var fiSub = map[rune]string{
'&': "ja",
'@': "at",
}
var grSub = map[rune]string{
'&': "kai",
'η': "i",
@ -51,7 +59,39 @@ var grSub = map[rune]string{
'Η': "i",
'ι': "i",
'ί': "i",
'ϊ': "i",
'Ι': "i",
'χ': "x",
'Χ': "x",
'ω': "w",
'ώ': "w",
'Ω': "w",
'ϋ': "u",
}
var nlSub = map[rune]string{
'&': "en",
'@': "at",
}
var plSub = map[rune]string{
'&': "i",
'@': "na",
}
var trSub = map[rune]string{
'&': "ve",
'@': "et",
'ş': "s",
'Ş': "s",
'ü': "u",
'Ü': "u",
'ö': "o",
'Ö': "o",
'İ': "i",
'ı': "i",
'ğ': "g",
'Ğ': "g",
'ç': "c",
'Ç': "c",
}

View File

@ -56,12 +56,18 @@ func MakeLang(s string, lang string) (slug string) {
slug = SubstituteRune(slug, deSub)
case "en":
slug = SubstituteRune(slug, enSub)
case "pl":
slug = SubstituteRune(slug, plSub)
case "es":
slug = SubstituteRune(slug, esSub)
case "fi":
slug = SubstituteRune(slug, fiSub)
case "gr":
slug = SubstituteRune(slug, grSub)
case "nl":
slug = SubstituteRune(slug, nlSub)
case "pl":
slug = SubstituteRune(slug, plSub)
case "tr":
slug = SubstituteRune(slug, trSub)
default: // fallback to "en" if lang not found
slug = SubstituteRune(slug, enSub)
}
@ -74,7 +80,7 @@ func MakeLang(s string, lang string) (slug string) {
// Process all remaining symbols
slug = regexpNonAuthorizedChars.ReplaceAllString(slug, "-")
slug = regexpMultipleDashes.ReplaceAllString(slug, "-")
slug = strings.Trim(slug, "-")
slug = strings.Trim(slug, "-_")
if MaxLength > 0 {
slug = smartTruncate(slug)

View File

@ -1,421 +0,0 @@
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package slug
import (
"testing"
)
//=============================================================================
func TestSlugMake(t *testing.T) {
var testCases = []struct {
in string
want string
}{
{"DOBROSLAWZYBORT", "dobroslawzybort"},
{"Dobroslaw Zybort", "dobroslaw-zybort"},
{" Dobroslaw Zybort ?", "dobroslaw-zybort"},
{"Dobrosław Żybort", "dobroslaw-zybort"},
{"Ala ma 6 kotów.", "ala-ma-6-kotow"},
{"áÁàÀãÃâÂäÄąĄą̊Ą̊", "aaaaaaaaaaaaaa"},
{"ćĆĉĈçÇ", "cccccc"},
{"éÉèÈẽẼêÊëËęĘ", "eeeeeeeeeeee"},
{"íÍìÌĩĨîÎïÏįĮ", "iiiiiiiiiiii"},
{"łŁ", "ll"},
{"ńŃ", "nn"},
{"óÓòÒõÕôÔöÖǫǪǭǬø", "ooooooooooooooo"},
{"śŚ", "ss"},
{"úÚùÙũŨûÛüÜųŲ", "uuuuuuuuuuuu"},
{"y̨Y̨", "yy"},
{"źŹżŹ", "zzzz"},
{"·/,:;`˜'\"", ""},
{"20002013", "2000-2013"},
{"style—not", "style-not"},
{"test_slug", "test_slug"},
{"Æ", "ae"},
{"Ich heiße", "ich-heisse"},
{"This & that", "this-and-that"},
{"fácil €", "facil-eu"},
{"smile ☺", "smile"},
{"Hellö Wörld хелло ворлд", "hello-world-khello-vorld"},
{"\"C'est déjà lété.\"", "cest-deja-lete"},
{"jaja---lol-méméméoo--a", "jaja-lol-mememeoo-a"},
{"影師", "ying-shi"},
}
for index, st := range testCases {
got := Make(st.in)
if got != st.want {
t.Errorf(
"%d. Make(%#v) = %#v; want %#v",
index, st.in, got, st.want)
}
}
}
func TestSlugMakeLang(t *testing.T) {
var testCases = []struct {
lang string
in string
want string
}{
{"en", "This & that", "this-and-that"},
{"de", "This & that", "this-und-that"},
{"pl", "This & that", "this-i-that"},
{"es", "This & that", "this-y-that"},
{"gr", "This & that", "this-kai-that"},
{"test", "This & that", "this-and-that"}, // unknown lang, fallback to "en"
}
for index, smlt := range testCases {
got := MakeLang(smlt.in, smlt.lang)
if got != smlt.want {
t.Errorf(
"%d. MakeLang(%#v, %#v) = %#v; want %#v",
index, smlt.in, smlt.lang, got, smlt.want)
}
}
}
func TestSlugMakeUserSubstituteLang(t *testing.T) {
var testCases = []struct {
cSub map[string]string
lang string
in string
want string
}{
{map[string]string{"'": " "}, "en", "That's great", "that-s-great"},
{map[string]string{"&": "or"}, "en", "This & that", "this-or-that"}, // by default "&" => "and"
{map[string]string{"&": "or"}, "de", "This & that", "this-or-that"}, // by default "&" => "und"
{map[string]string{"&": "or", "@": "the"}, "de", "@ This & that", "the-this-or-that"}, // by default "&" => "und", "@" => "an"
}
for index, smust := range testCases {
CustomSub = smust.cSub
got := MakeLang(smust.in, smust.lang)
if got != smust.want {
t.Errorf(
"%d. %#v; MakeLang(%#v, %#v) = %#v; want %#v",
index, smust.cSub, smust.in, smust.lang,
got, smust.want)
}
}
}
func TestSlugMakeSubstituteOrderLang(t *testing.T) {
// Always substitute runes first
var testCases = []struct {
rSub map[rune]string
sSub map[string]string
in string
want string
}{
{map[rune]string{'o': "left"}, map[string]string{"o": "right"}, "o o", "left-left"},
{map[rune]string{'o': "left", 'a': "r"}, map[string]string{"o": "right"}, "o a o", "left-r-left"},
{map[rune]string{'o': "left"}, map[string]string{"o": "right", "a": "r"}, "a o a o", "r-left-r-left"},
{map[rune]string{'&': "down"}, map[string]string{"&": "up"}, "&", "down"},
}
for index, smsot := range testCases {
CustomRuneSub = smsot.rSub
CustomSub = smsot.sSub
got := Make(smsot.in)
if got != smsot.want {
t.Errorf(
"%d. %#v; %#v; Make(%#v) = %#v; want %#v",
index, smsot.rSub, smsot.sSub, smsot.in,
got, smsot.want)
}
}
}
func TestSubstituteLang(t *testing.T) {
var testCases = []struct {
cSub map[string]string
in string
want string
}{
{map[string]string{"o": "no"}, "o o o", "no no no"},
{map[string]string{"o": "no", "a": "or"}, "o a o", "no nor no"},
{map[string]string{"a": "or", "o": "no"}, "o a o", "no nor no"},
{map[string]string{"'": " "}, "That's great", "That s great"},
}
for index, sst := range testCases {
got := Substitute(sst.in, sst.cSub)
if got != sst.want {
t.Errorf(
"%d. Substitute(%#v, %#v) = %#v; want %#v",
index, sst.in, sst.cSub, got, sst.want)
}
}
}
func TestSubstituteRuneLang(t *testing.T) {
var testCases = []struct {
cSub map[rune]string
in string
want string
}{
{map[rune]string{'o': "no"}, "o o o", "no no no"},
{map[rune]string{'o': "no", 'a': "or"}, "o a o", "no or no"},
{map[rune]string{'a': "or", 'o': "no"}, "o a o", "no or no"},
{map[rune]string{'\'': " "}, "That's great", "That s great"},
}
for index, ssrt := range testCases {
got := SubstituteRune(ssrt.in, ssrt.cSub)
if got != ssrt.want {
t.Errorf(
"%d. SubstituteRune(%#v, %#v) = %#v; want %#v",
index, ssrt.in, ssrt.cSub, got, ssrt.want)
}
}
}
func TestSlugMakeSmartTruncate(t *testing.T) {
var testCases = []struct {
in string
maxLength int
want string
}{
{"DOBROSLAWZYBORT", 100, "dobroslawzybort"},
{"Dobroslaw Zybort", 100, "dobroslaw-zybort"},
{"Dobroslaw Zybort", 12, "dobroslaw"},
{" Dobroslaw Zybort ?", 12, "dobroslaw"},
{"Ala ma 6 kotów.", 10, "ala-ma-6"},
{"Dobrosław Żybort", 5, "dobro"},
}
for index, smstt := range testCases {
MaxLength = smstt.maxLength
got := Make(smstt.in)
if got != smstt.want {
t.Errorf(
"%d. MaxLength = %v; Make(%#v) = %#v; want %#v",
index, smstt.maxLength, smstt.in, got, smstt.want)
}
}
}
func TestIsSlug(t *testing.T) {
MaxLength = 0
type args struct {
text string
}
tests := []struct {
name string
args args
want bool
}{
{"some", args{"some"}, true},
{"with -", args{"some-more"}, true},
{"with _", args{"some_more"}, true},
{"with numbers", args{"number-2"}, true},
{"empty string", args{""}, false},
{"upper case", args{"Some-more"}, false},
{"space", args{"some more"}, false},
{"starts with '-'", args{"-some"}, false},
{"ends with '-'", args{"some-"}, false},
{"starts with '_'", args{"_some"}, false},
{"ends with '_'", args{"some_"}, false},
{"outside ASCII", args{"Dobrosław Żybort"}, false},
{"outside ASCII ", args{"20002013"}, false},
{"smile ☺", args{"smile ☺"}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsSlug(tt.args.text); got != tt.want {
t.Errorf("IsSlug() = %v, want %v", got, tt.want)
}
})
}
t.Run("MaxLength", func(t *testing.T) {
MaxLength = 4
if got := IsSlug("012345"); got != false {
t.Errorf("IsSlug() = %v, want %v", got, false)
}
MaxLength = 0
})
}
func BenchmarkMakeShortAscii(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
Make("Hello world")
}
}
func BenchmarkMakeShort(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
Make("хелло ворлд")
}
}
func BenchmarkMakeShortSymbols(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
Make("·/,:;`˜'\" &€£¥")
}
}
func BenchmarkMakeMediumAscii(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
Make("ABCDE FGHIJ KLMNO PQRST UWXYZ ABCDE FGHIJ KLMNO PQRST UWXYZ ABCDE")
}
}
func BenchmarkMakeMedium(b *testing.B) {
b.ReportAllocs()
for n := 0; n < b.N; n++ {
Make("ヲァィゥェ ォャュョッ ーアイウエ オカキクケ コサシスセ ソタチツテ トナニヌネ ノハヒフヘ ホマミムメ モヤユヨラ リルレロワ")
}
}
func BenchmarkMakeLongAscii(b *testing.B) {
longStr := "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi " +
"pulvinar sodales ultrices. Nulla facilisi. Sed at vestibulum erat. Ut " +
"sit amet urna posuere, sagittis eros ac, varius nisi. Morbi ullamcorper " +
"odio at nunc pulvinar mattis. Vestibulum rutrum, ante eu dictum mattis, " +
"elit risus finibus nunc, consectetur facilisis eros leo ut sapien. Sed " +
"pulvinar volutpat mi. Cras semper mi ac eros accumsan, at feugiat massa " +
"elementum. Morbi eget dolor sit amet purus condimentum egestas non ut " +
"sapien. Duis feugiat magna vitae nisi lobortis, quis finibus sem " +
"sollicitudin. Pellentesque eleifend blandit ipsum, ut porta arcu " +
"ultricies et. Fusce vel ipsum porta, placerat diam ac, consectetur " +
"magna. Nulla in porta sem. Suspendisse commodo, felis in molestie " +
"ultricies, arcu ipsum aliquet turpis, elementum dapibus ipsum lorem a " +
"nisl. Etiam varius imperdiet placerat. Aliquam euismod lacus arcu, " +
"ultrices hendrerit est pellentesque vel. Aliquam sit amet laoreet leo. " +
"Integer eros libero, mollis sed posuere."
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
Make(longStr)
}
}
func BenchmarkSubstituteRuneShort(b *testing.B) {
shortStr := "Hello/Hi world"
subs := map[rune]string{'o': "no", '/': "slash"}
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
SubstituteRune(shortStr, subs)
}
}
func BenchmarkSubstituteRuneLong(b *testing.B) {
longStr := "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi " +
"pulvinar sodales ultrices. Nulla facilisi. Sed at vestibulum erat. Ut " +
"sit amet urna posuere, sagittis eros ac, varius nisi. Morbi ullamcorper " +
"odio at nunc pulvinar mattis. Vestibulum rutrum, ante eu dictum mattis, " +
"elit risus finibus nunc, consectetur facilisis eros leo ut sapien. Sed " +
"pulvinar volutpat mi. Cras semper mi ac eros accumsan, at feugiat massa " +
"elementum. Morbi eget dolor sit amet purus condimentum egestas non ut " +
"sapien. Duis feugiat magna vitae nisi lobortis, quis finibus sem " +
"sollicitudin. Pellentesque eleifend blandit ipsum, ut porta arcu " +
"ultricies et. Fusce vel ipsum porta, placerat diam ac, consectetur " +
"magna. Nulla in porta sem. Suspendisse commodo, felis in molestie " +
"ultricies, arcu ipsum aliquet turpis, elementum dapibus ipsum lorem a " +
"nisl. Etiam varius imperdiet placerat. Aliquam euismod lacus arcu, " +
"ultrices hendrerit est pellentesque vel. Aliquam sit amet laoreet leo. " +
"Integer eros libero, mollis sed posuere."
subs := map[rune]string{
'o': "no",
'/': "slash",
'i': "done",
'E': "es",
'a': "ASD",
'1': "one",
'l': "onetwo",
}
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
SubstituteRune(longStr, subs)
}
}
func BenchmarkSmartTruncateShort(b *testing.B) {
shortStr := "Hello-world"
MaxLength = 8
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
smartTruncate(shortStr)
}
}
func BenchmarkSmartTruncateLong(b *testing.B) {
longStr := "Lorem-ipsum-dolor-sit-amet,-consectetur-adipiscing-elit.-Morbi-" +
"pulvinar-sodales-ultrices.-Nulla-facilisi.-Sed-at-vestibulum-erat.-Ut-" +
"sit-amet-urna-posuere,-sagittis-eros-ac,-varius-nisi.-Morbi-ullamcorper-" +
"odio-at-nunc-pulvinar-mattis.-Vestibulum-rutrum,-ante-eu-dictum-mattis,-" +
"elit-risus-finibus-nunc,-consectetur-facilisis-eros-leo-ut-sapien.-Sed-" +
"pulvinar-volutpat-mi.-Cras-semper-mi-ac-eros-accumsan,-at-feugiat-massa-" +
"elementum.-Morbi-eget-dolor-sit-amet-purus-condimentum-egestas-non-ut-" +
"sapien.-Duis-feugiat-magna-vitae-nisi-lobortis,-quis-finibus-sem-" +
"sollicitudin.-Pellentesque-eleifend-blandit-ipsum,-ut-porta-arcu-" +
"ultricies-et.-Fusce-vel-ipsum-porta,-placerat-diam-ac,-consectetur-" +
"magna.-Nulla-in-porta-sem.-Suspendisse-commodo,-felis-in-molestie-" +
"ultricies,-arcu-ipsum-aliquet-turpis,-elementum-dapibus-ipsum-lorem-a-" +
"nisl.-Etiam-varius-imperdiet-placerat.-Aliquam-euismod-lacus-arcu,-" +
"ultrices-hendrerit-est-pellentesque-vel.-Aliquam-sit-amet-laoreet-leo.-" +
"Integer-eros-libero,-mollis-sed-posuere."
MaxLength = 256
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
smartTruncate(longStr)
}
}
func BenchmarkIsSlugShort(b *testing.B) {
shortStr := "hello-world"
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
IsSlug(shortStr)
}
}
func BenchmarkIsSlugLong(b *testing.B) {
longStr := "lorem-ipsum-dolor-sit-amet-consectetur-adipiscing-elit-morbi-" +
"pulvinar-sodales-ultrices-nulla-facilisi-sed-at-vestibulum-erat-ut-" +
"sit-amet-urna-posuere-sagittis-eros-ac-varius-nisi-morbi-ullamcorper-" +
"odio-at-nunc-pulvinar-mattis-vestibulum-rutrum-ante-eu-dictum-mattis,-" +
"elit-risus-finibus-nunc-consectetur-facilisis-eros-leo-ut-sapien-sed-" +
"pulvinar-volutpat-mi-cras-semper-mi-ac-eros-accumsan-at-feugiat-massa-" +
"elementum-morbi-eget-dolor-sit-amet-purus-condimentum-egestas-non-ut-" +
"sapien-duis-feugiat-magna-vitae-nisi-lobortis-quis-finibus-sem-" +
"sollicitudin-pellentesque-eleifend-blandit-ipsum-ut-porta-arcu-" +
"ultricies-et-fusce-vel-ipsum-porta-placerat-diam-ac-consectetur-" +
"magna-nulla-in-porta-sem-suspendisse-commodo-felis-in-molestie-" +
"ultricies-arcu-ipsum-aliquet-turpis-elementum-dapibus-ipsum-lorem-a-" +
"nisl-etiam-varius-imperdiet-placerat-aliquam-euismod-lacus-arcu-" +
"ultrices-hendrerit-est-pellentesque-vel-aliquam-sit-amet-laoreet-leo-" +
"integer-eros-libero-mollis-sed-posuere"
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
IsSlug(longStr)
}
}

View File

@ -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.

View File

@ -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
}

View File

@ -0,0 +1,11 @@
// +build linux darwin
package sequences
import (
"fmt"
)
func EnableVirtualTerminalProcessing(stream uintptr, enable bool) error {
return fmt.Errorf("windows only package")
}

View File

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

View File

@ -1,6 +0,0 @@
unidecode
=========
Unicode transliterator in Golang - Replaces non-ASCII characters with their ASCII approximations.
[![GoDoc](https://godoc.org/github.com/rainycape/unidecode?status.svg)](https://godoc.org/github.com/rainycape/unidecode)

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
package unidecode
import (
"testing"
)
func testTransliteration(original string, decoded string, t *testing.T) {
if r := Unidecode(original); r != decoded {
t.Errorf("Expected '%s', got '%s'\n", decoded, r)
}
}
func TestASCII(t *testing.T) {
s := "ABCDEF"
testTransliteration(s, s, t)
}
func TestKnosos(t *testing.T) {
o := "Κνωσός"
d := "Knosos"
testTransliteration(o, d, t)
}
func TestBeiJing(t *testing.T) {
o := "\u5317\u4EB0"
d := "Bei Jing "
testTransliteration(o, d, t)
}
func TestEmoji(t *testing.T) {
o := "Hey Luna t belle 😵😂"
d := "Hey Luna t belle "
testTransliteration(o, d, t)
}
func BenchmarkUnidecode(b *testing.B) {
cases := []string{
"ABCDEF",
"Κνωσός",
"\u5317\u4EB0",
}
for ii := 0; ii < b.N; ii++ {
for _, v := range cases {
_ = Unidecode(v)
}
}
}
func BenchmarkDecodeTable(b *testing.B) {
for ii := 0; ii < b.N; ii++ {
decodeTransliterations()
}
}
func init() {
decodeTransliterations()
}

View File

@ -1 +0,0 @@
logrus

View File

@ -1,15 +0,0 @@
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 ./...

View File

@ -1,118 +0,0 @@
# 1.0.4
* Fix race when adding hooks (#612)
* Fix terminal check in AppEngine (#635)
# 1.0.3
* Replace example files with testable examples
# 1.0.2
* bug: quote non-string values in text formatter (#583)
* Make (*Logger) SetLevel a public method
# 1.0.1
* bug: fix escaping in text formatter (#575)
# 1.0.0
* Officially changed name to lower-case
* bug: colors on Windows 10 (#541)
* bug: fix race in accessing level (#512)
# 0.11.5
* feature: add writer and writerlevel to entry (#372)
# 0.11.4
* bug: fix undefined variable on solaris (#493)
# 0.11.3
* formatter: configure quoting of empty values (#484)
* formatter: configure quoting character (default is `"`) (#484)
* bug: fix not importing io correctly in non-linux environments (#481)
# 0.11.2
* bug: fix windows terminal detection (#476)
# 0.11.1
* bug: fix tty detection with custom out (#471)
# 0.11.0
* performance: Use bufferpool to allocate (#370)
* terminal: terminal detection for app-engine (#343)
* feature: exit handler (#375)
# 0.10.0
* feature: Add a test hook (#180)
* feature: `ParseLevel` is now case-insensitive (#326)
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
* performance: avoid re-allocations on `WithFields` (#335)
# 0.9.0
* logrus/text_formatter: don't emit empty msg
* logrus/hooks/airbrake: move out of main repository
* logrus/hooks/sentry: move out of main repository
* logrus/hooks/papertrail: move out of main repository
* logrus/hooks/bugsnag: move out of main repository
* logrus/core: run tests with `-race`
* logrus/core: detect TTY based on `stderr`
* logrus/core: support `WithError` on logger
* logrus/core: Solaris support
# 0.8.7
* logrus/core: fix possible race (#216)
* logrus/doc: small typo fixes and doc improvements
# 0.8.6
* hooks/raven: allow passing an initialized client
# 0.8.5
* logrus/core: revert #208
# 0.8.4
* formatter/text: fix data race (#218)
# 0.8.3
* logrus/core: fix entry log level (#208)
* logrus/core: improve performance of text formatter by 40%
* logrus/core: expose `LevelHooks` type
* logrus/core: add support for DragonflyBSD and NetBSD
* formatter/text: print structs more verbosely
# 0.8.2
* logrus: fix more Fatal family functions
# 0.8.1
* logrus: fix not exiting on `Fatalf` and `Fatalln`
# 0.8.0
* logrus: defaults to stderr instead of stdout
* hooks/sentry: add special field for `*http.Request`
* formatter/text: ignore Windows for colors
# 0.7.3
* formatter/\*: allow configuration of timestamp layout
# 0.7.2
* formatter/text: Add configuration option for time format (#158)

View File

@ -1,509 +0,0 @@
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>&nbsp;[![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus)&nbsp;[![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus)
Logrus is a structured logger for Go (golang), completely API compatible with
the standard library logger.
**Seeing weird case-sensitive problems?** It's in the past been possible to
import Logrus as both upper- and lower-case. Due to the Go package environment,
this caused issues in the community and we needed a standard. Some environments
experienced problems with the upper-case variant, so the lower-case was decided.
Everything using `logrus` will need to use the lower-case:
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
To fix Glide, see [these
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
For an in-depth explanation of the casing issue, see [this
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
**Are you interested in assisting in maintaining Logrus?** Currently I have a
lot of obligations, and I am unable to provide Logrus with the maintainership it
needs. If you'd like to help, please reach out to me at `simon at author's
username dot com`.
Nicely color-coded in development (when a TTY is attached, otherwise just
plain text):
![Colored](http://i.imgur.com/PY7qMwd.png)
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
or Splunk:
```json
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
{"level":"warning","msg":"The group's number increased tremendously!",
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
```
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
attached, the output is compatible with the
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
```text
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
exit status 1
```
#### Case-sensitivity
The organization's name was changed to lower-case--and this will not be changed
back. If you are getting import conflicts due to case sensitivity, please use
the lower-case import: `github.com/sirupsen/logrus`.
#### Example
The simplest way to use Logrus is simply the package-level exported logger:
```go
package main
import (
log "github.com/sirupsen/logrus"
)
func main() {
log.WithFields(log.Fields{
"animal": "walrus",
}).Info("A walrus appears")
}
```
Note that it's completely api-compatible with the stdlib logger, so you can
replace your `log` imports everywhere with `log "github.com/sirupsen/logrus"`
and you'll now have the flexibility of Logrus. You can customize it all you
want:
```go
package main
import (
"os"
log "github.com/sirupsen/logrus"
)
func init() {
// Log as JSON instead of the default ASCII formatter.
log.SetFormatter(&log.JSONFormatter{})
// Output to stdout instead of the default stderr
// Can be any io.Writer, see below for File example
log.SetOutput(os.Stdout)
// Only log the warning severity or above.
log.SetLevel(log.WarnLevel)
}
func main() {
log.WithFields(log.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(log.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(log.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
// A common pattern is to re-use fields between logging statements by re-using
// the logrus.Entry returned from WithFields()
contextLogger := log.WithFields(log.Fields{
"common": "this is a common field",
"other": "I also should be logged always",
})
contextLogger.Info("I'll be logged with common and other field")
contextLogger.Info("Me too")
}
```
For more advanced usage such as logging to multiple locations from the same
application, you can also create an instance of the `logrus` Logger:
```go
package main
import (
"os"
"github.com/sirupsen/logrus"
)
// Create a new instance of the logger. You can have any number of instances.
var log = logrus.New()
func main() {
// The API for setting attributes is a little different than the package level
// exported logger. See Godoc.
log.Out = os.Stdout
// You could set this to any `io.Writer` such as a file
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
// if err == nil {
// log.Out = file
// } else {
// log.Info("Failed to log to file, using default stderr")
// }
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
}
```
#### Fields
Logrus encourages careful, structured logging through logging fields instead of
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
to send event %s to topic %s with key %d")`, you should log the much more
discoverable:
```go
log.WithFields(log.Fields{
"event": event,
"topic": topic,
"key": key,
}).Fatal("Failed to send event")
```
We've found this API forces you to think about logging in a way that produces
much more useful logging messages. We've been in countless situations where just
a single added field to a log statement that was already there would've saved us
hours. The `WithFields` call is optional.
In general, with Logrus using any of the `printf`-family functions should be
seen as a hint you should add a field, however, you can still use the
`printf`-family functions with Logrus.
#### Default Fields
Often it's helpful to have fields _always_ attached to log statements in an
application or parts of one. For example, you may want to always log the
`request_id` and `user_ip` in the context of a request. Instead of writing
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
every line, you can create a `logrus.Entry` to pass around instead:
```go
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger.Info("something happened on that request") # will log request_id and user_ip
requestLogger.Warn("something not great happened")
```
#### Hooks
You can add hooks for logging levels. For example to send errors to an exception
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
multiple places simultaneously, e.g. syslog.
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
`init`:
```go
import (
log "github.com/sirupsen/logrus"
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
"log/syslog"
)
func init() {
// Use the Airbrake hook to report errors that have Error severity or above to
// an exception tracker. You can create custom hooks, see the Hooks section.
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err != nil {
log.Error("Unable to connect to local syslog daemon")
} else {
log.AddHook(hook)
}
}
```
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) |
| [AzureTableHook](https://github.com/kpfaulkner/azuretablehook/) | Hook for logging to Azure Table Storage|
| [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/tracer0tong/kafkalogrus) | Hook for logging to Kafka |
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
| [Logbeat](https://github.com/macandmia/logbeat) | Hook for logging to [Opbeat](https://opbeat.com/) |
| [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) |
| [Promrus](https://github.com/weaveworks/promrus) | Expose number of log messages as [Prometheus](https://prometheus.io/) metrics |
| [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. |
| [Telegram](https://github.com/rossmcdonald/telegram_hook) | Hook for logging errors to [Telegram](https://telegram.org/) |
| [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/) |
#### Level logging
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
```go
log.Debug("Useful debugging information.")
log.Info("Something noteworthy happened!")
log.Warn("You should probably take a look at this.")
log.Error("Something failed but I'm not quitting.")
// Calls os.Exit(1) after logging
log.Fatal("Bye.")
// Calls panic() after logging
log.Panic("I'm bailing.")
```
You can set the logging level on a `Logger`, then it will only log entries with
that severity or anything above it:
```go
// Will log anything that is info or above (warn, error, fatal, panic). Default.
log.SetLevel(log.InfoLevel)
```
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
environment if your application has that.
#### Entries
Besides the fields added with `WithField` or `WithFields` some fields are
automatically added to all logging events:
1. `time`. The timestamp when the entry was created.
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
the `AddFields` call. E.g. `Failed to send event.`
3. `level`. The logging level. E.g. `info`.
#### Environments
Logrus has no notion of environment.
If you wish for hooks and formatters to only be used in specific environments,
you should handle that yourself. For example, if your application has a global
variable `Environment`, which is a string representation of the environment you
could do:
```go
import (
log "github.com/sirupsen/logrus"
)
init() {
// do something here to set environment depending on an environment variable
// or command-line flag
if Environment == "production" {
log.SetFormatter(&log.JSONFormatter{})
} else {
// The TextFormatter is default, you don't actually have to do this.
log.SetFormatter(&log.TextFormatter{})
}
}
```
This configuration is how `logrus` was intended to be used, but JSON in
production is mostly only useful if you do log aggregation with tools like
Splunk or Logstash.
#### Formatters
The built-in logging formatters are:
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
without colors.
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
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).
* 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 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̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
You can define your formatter by implementing the `Formatter` interface,
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
`Fields` type (`map[string]interface{}`) with all your fields as well as the
default ones (see Entries section above):
```go
type MyJSONFormatter struct {
}
log.SetFormatter(new(MyJSONFormatter))
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
// Note this doesn't include Time, Level and Message which are available on
// the Entry. Consult `godoc` on information about those fields or read the
// source of the official loggers.
serialized, err := json.Marshal(entry.Data)
if err != nil {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
}
return append(serialized, '\n'), nil
}
```
#### Logger as an `io.Writer`
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
```go
w := logger.Writer()
defer w.Close()
srv := http.Server{
// create a stdlib log.Logger that writes to
// logrus.Logger.
ErrorLog: log.New(w, "", 0),
}
```
Each line written to that writer will be printed the usual way, using formatters
and hooks. The level for those entries is `info`.
This means that we can override the standard library logger easily:
```go
logger := logrus.New()
logger.Formatter = &logrus.JSONFormatter{}
// Use logrus for standard log output
// Note that `log` here references stdlib's log
// Not logrus imported under the name `log`.
log.SetOutput(logger.Writer())
```
#### Rotation
Log rotation is not provided with Logrus. Log rotation should be done by an
external program (like `logrotate(8)`) that can compress and delete old log
entries. It should not be a feature of the application-level logger.
#### Tools
| Tool | Description |
| ---- | ----------- |
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
#### Testing
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just add the `test` hook
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
```go
import(
"github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"testing"
)
func TestSomething(t*testing.T){
logger, hook := test.NewNullLogger()
logger.Error("Helloerror")
assert.Equal(t, 1, len(hook.Entries))
assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
assert.Equal(t, "Helloerror", hook.LastEntry().Message)
hook.Reset()
assert.Nil(t, hook.LastEntry())
}
```
#### Fatal handlers
Logrus can register one or more functions that will be called when any `fatal`
level message is logged. The registered handlers will be executed before
logrus performs a `os.Exit(1)`. This behavior may be helpful if callers need
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
```
...
handler := func() {
// gracefully shutdown something...
}
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.
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:
* You have no hooks registered, or hooks calling is already thread-safe.
* Writing to logger.Out is already thread-safe, for example:
1) logger.Out is protected by locks.
2) logger.Out is a os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allow multi-thread/multi-process writing)
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)

View File

@ -51,9 +51,9 @@ func Exit(code int) {
os.Exit(code)
}
// RegisterExitHandler adds a Logrus Exit handler, call logrus.Exit to invoke
// all handlers. The handlers will also be invoked when any Fatal log entry is
// made.
// RegisterExitHandler appends a Logrus Exit handler to the list of handlers,
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
// any Fatal log entry is made.
//
// This method is useful when a caller wishes to use logrus to log a fatal
// message but also needs to gracefully shutdown. An example usecase could be
@ -62,3 +62,15 @@ func Exit(code int) {
func RegisterExitHandler(handler func()) {
handlers = append(handlers, handler)
}
// DeferExitHandler prepends a Logrus Exit handler to the list of handlers,
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
// any Fatal log entry is made.
//
// This method is useful when a caller wishes to use logrus to log a fatal
// message but also needs to gracefully shutdown. An example usecase could be
// closing database connections, or sending a alert that the application is
// closing.
func DeferExitHandler(handler func()) {
handlers = append([]func(){handler}, handlers...)
}

View File

@ -1,83 +0,0 @@
package logrus
import (
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
)
func TestRegister(t *testing.T) {
current := len(handlers)
RegisterExitHandler(func() {})
if len(handlers) != current+1 {
t.Fatalf("expected %d handlers, got %d", current+1, len(handlers))
}
}
func TestHandler(t *testing.T) {
tempDir, err := ioutil.TempDir("", "test_handler")
if err != nil {
log.Fatalf("can't create temp dir. %q", err)
}
defer os.RemoveAll(tempDir)
gofile := filepath.Join(tempDir, "gofile.go")
if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil {
t.Fatalf("can't create go file. %q", err)
}
outfile := filepath.Join(tempDir, "outfile.out")
arg := time.Now().UTC().String()
err = exec.Command("go", "run", gofile, outfile, arg).Run()
if err == nil {
t.Fatalf("completed normally, should have failed")
}
data, err := ioutil.ReadFile(outfile)
if err != nil {
t.Fatalf("can't read output file %s. %q", outfile, err)
}
if string(data) != arg {
t.Fatalf("bad data. Expected %q, got %q", data, arg)
}
}
var testprog = []byte(`
// Test program for atexit, gets output file and data as arguments and writes
// data to output file in atexit handler.
package main
import (
"github.com/sirupsen/logrus"
"flag"
"fmt"
"io/ioutil"
)
var outfile = ""
var data = ""
func handler() {
ioutil.WriteFile(outfile, []byte(data), 0666)
}
func badHandler() {
n := 0
fmt.Println(1/n)
}
func main() {
flag.Parse()
outfile = flag.Arg(0)
data = flag.Arg(1)
logrus.RegisterExitHandler(handler)
logrus.RegisterExitHandler(badHandler)
logrus.Fatal("Bye bye")
}
`)

View File

@ -1,14 +0,0 @@
version: "{build}"
platform: x64
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
environment:
GOPATH: c:\gopath
branches:
only:
- master
install:
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
- go version
build_script:
- go get -t
- go test

View File

@ -2,13 +2,33 @@ package logrus
import (
"bytes"
"context"
"fmt"
"os"
"reflect"
"runtime"
"strings"
"sync"
"time"
)
var bufferPool *sync.Pool
var (
bufferPool *sync.Pool
// qualified package name, cached at first use
logrusPackage string
// Positions in the call stack when tracing to report the calling method
minimumCallerDepth int
// Used for caller information initialisation
callerInitOnce sync.Once
)
const (
maximumCallerDepth int = 25
knownLogrusFrames int = 4
)
func init() {
bufferPool = &sync.Pool{
@ -16,15 +36,18 @@ func init() {
return new(bytes.Buffer)
},
}
// start at the bottom of the stack before the package-name cache is primed
minimumCallerDepth = 1
}
// Defines the key when adding errors using WithError.
var ErrorKey = "error"
// An entry is the final or intermediate Logrus logging entry. It contains all
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
// passed around as much as you wish to avoid field duplication.
// the fields passed with WithField{,s}. It's finally logged when Trace, Debug,
// Info, Warn, Error, Fatal or Panic is called on it. These objects can be
// reused and passed around as much as you wish to avoid field duplication.
type Entry struct {
Logger *Logger
@ -34,22 +57,31 @@ type Entry struct {
// Time at which the log entry was created
Time time.Time
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
// Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
Level Level
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
// Calling method, with package name
Caller *runtime.Frame
// Message passed to Trace, 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
// Contains the context set by the user. Useful for hook processing etc.
Context context.Context
// err may contain a field formatting error
err string
}
func NewEntry(logger *Logger) *Entry {
return &Entry{
Logger: logger,
// Default is three fields, give a little extra room
Data: make(Fields, 5),
// Default is three fields, plus one optional. Give a little extra room.
Data: make(Fields, 6),
}
}
@ -69,6 +101,11 @@ func (entry *Entry) WithError(err error) *Entry {
return entry.WithField(ErrorKey, err)
}
// Add a context to the Entry.
func (entry *Entry) WithContext(ctx context.Context) *Entry {
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: entry.Time, err: entry.err, Context: ctx}
}
// Add a single field to the Entry.
func (entry *Entry) WithField(key string, value interface{}) *Entry {
return entry.WithFields(Fields{key: value})
@ -80,46 +117,120 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
for k, v := range entry.Data {
data[k] = v
}
fieldErr := entry.err
for k, v := range fields {
isErrField := false
if t := reflect.TypeOf(v); t != nil {
switch t.Kind() {
case reflect.Func:
isErrField = true
case reflect.Ptr:
isErrField = t.Elem().Kind() == reflect.Func
}
}
if isErrField {
tmp := fmt.Sprintf("can not add field %q", k)
if fieldErr != "" {
fieldErr = entry.err + ", " + tmp
} else {
fieldErr = tmp
}
} else {
data[k] = v
}
return &Entry{Logger: entry.Logger, Data: data}
}
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
}
// Overrides the time of the Entry.
func (entry *Entry) WithTime(t time.Time) *Entry {
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t, err: entry.err, Context: entry.Context}
}
// getPackageName reduces a fully qualified function name to the package name
// There really ought to be to be a better way...
func getPackageName(f string) string {
for {
lastPeriod := strings.LastIndex(f, ".")
lastSlash := strings.LastIndex(f, "/")
if lastPeriod > lastSlash {
f = f[:lastPeriod]
} else {
break
}
}
return f
}
// getCaller retrieves the name of the first non-logrus calling function
func getCaller() *runtime.Frame {
// cache this package's fully-qualified name
callerInitOnce.Do(func() {
pcs := make([]uintptr, 2)
_ = runtime.Callers(0, pcs)
logrusPackage = getPackageName(runtime.FuncForPC(pcs[1]).Name())
// now that we have the cache, we can skip a minimum count of known-logrus functions
// XXX this is dubious, the number of frames may vary
minimumCallerDepth = knownLogrusFrames
})
// Restrict the lookback frames to avoid runaway lookups
pcs := make([]uintptr, maximumCallerDepth)
depth := runtime.Callers(minimumCallerDepth, pcs)
frames := runtime.CallersFrames(pcs[:depth])
for f, again := frames.Next(); again; f, again = frames.Next() {
pkg := getPackageName(f.Function)
// If the caller isn't part of this package, we're done
if pkg != logrusPackage {
return &f
}
}
// if we got here, we failed to find the caller's context
return nil
}
func (entry Entry) HasCaller() (has bool) {
return entry.Logger != nil &&
entry.Logger.ReportCaller &&
entry.Caller != nil
}
// 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
// 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
entry.Logger.mu.Lock()
err := entry.Logger.Hooks.Fire(level, &entry)
entry.Logger.mu.Unlock()
if err != nil {
entry.Logger.mu.Lock()
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
entry.Logger.mu.Unlock()
if entry.Logger.ReportCaller {
entry.Caller = getCaller()
}
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
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
@ -129,26 +240,53 @@ func (entry Entry) log(level Level, msg string) {
}
}
func (entry *Entry) Debug(args ...interface{}) {
if entry.Logger.level() >= DebugLevel {
entry.log(DebugLevel, fmt.Sprint(args...))
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 {
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
} else {
_, err = entry.Logger.Out.Write(serialized)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
}
}
}
func (entry *Entry) Log(level Level, args ...interface{}) {
if entry.Logger.IsLevelEnabled(level) {
entry.log(level, fmt.Sprint(args...))
}
}
func (entry *Entry) Trace(args ...interface{}) {
entry.Log(TraceLevel, args...)
}
func (entry *Entry) Debug(args ...interface{}) {
entry.Log(DebugLevel, args...)
}
func (entry *Entry) Print(args ...interface{}) {
entry.Info(args...)
}
func (entry *Entry) Info(args ...interface{}) {
if entry.Logger.level() >= InfoLevel {
entry.log(InfoLevel, fmt.Sprint(args...))
}
entry.Log(InfoLevel, args...)
}
func (entry *Entry) Warn(args ...interface{}) {
if entry.Logger.level() >= WarnLevel {
entry.log(WarnLevel, fmt.Sprint(args...))
}
entry.Log(WarnLevel, args...)
}
func (entry *Entry) Warning(args ...interface{}) {
@ -156,37 +294,37 @@ func (entry *Entry) Warning(args ...interface{}) {
}
func (entry *Entry) Error(args ...interface{}) {
if entry.Logger.level() >= ErrorLevel {
entry.log(ErrorLevel, fmt.Sprint(args...))
}
entry.Log(ErrorLevel, args...)
}
func (entry *Entry) Fatal(args ...interface{}) {
if entry.Logger.level() >= FatalLevel {
entry.log(FatalLevel, fmt.Sprint(args...))
}
Exit(1)
entry.Log(FatalLevel, args...)
entry.Logger.Exit(1)
}
func (entry *Entry) Panic(args ...interface{}) {
if entry.Logger.level() >= PanicLevel {
entry.log(PanicLevel, fmt.Sprint(args...))
}
entry.Log(PanicLevel, args...)
panic(fmt.Sprint(args...))
}
// Entry Printf family functions
func (entry *Entry) Debugf(format string, args ...interface{}) {
if entry.Logger.level() >= DebugLevel {
entry.Debug(fmt.Sprintf(format, args...))
func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
if entry.Logger.IsLevelEnabled(level) {
entry.Log(level, fmt.Sprintf(format, args...))
}
}
func (entry *Entry) Tracef(format string, args ...interface{}) {
entry.Logf(TraceLevel, format, args...)
}
func (entry *Entry) Debugf(format string, args ...interface{}) {
entry.Logf(DebugLevel, format, args...)
}
func (entry *Entry) Infof(format string, args ...interface{}) {
if entry.Logger.level() >= InfoLevel {
entry.Info(fmt.Sprintf(format, args...))
}
entry.Logf(InfoLevel, format, args...)
}
func (entry *Entry) Printf(format string, args ...interface{}) {
@ -194,9 +332,7 @@ func (entry *Entry) Printf(format string, args ...interface{}) {
}
func (entry *Entry) Warnf(format string, args ...interface{}) {
if entry.Logger.level() >= WarnLevel {
entry.Warn(fmt.Sprintf(format, args...))
}
entry.Logf(WarnLevel, format, args...)
}
func (entry *Entry) Warningf(format string, args ...interface{}) {
@ -204,36 +340,36 @@ func (entry *Entry) Warningf(format string, args ...interface{}) {
}
func (entry *Entry) Errorf(format string, args ...interface{}) {
if entry.Logger.level() >= ErrorLevel {
entry.Error(fmt.Sprintf(format, args...))
}
entry.Logf(ErrorLevel, format, args...)
}
func (entry *Entry) Fatalf(format string, args ...interface{}) {
if entry.Logger.level() >= FatalLevel {
entry.Fatal(fmt.Sprintf(format, args...))
}
Exit(1)
entry.Logf(FatalLevel, format, args...)
entry.Logger.Exit(1)
}
func (entry *Entry) Panicf(format string, args ...interface{}) {
if entry.Logger.level() >= PanicLevel {
entry.Panic(fmt.Sprintf(format, args...))
}
entry.Logf(PanicLevel, format, args...)
}
// Entry Println family functions
func (entry *Entry) Debugln(args ...interface{}) {
if entry.Logger.level() >= DebugLevel {
entry.Debug(entry.sprintlnn(args...))
func (entry *Entry) Logln(level Level, args ...interface{}) {
if entry.Logger.IsLevelEnabled(level) {
entry.Log(level, entry.sprintlnn(args...))
}
}
func (entry *Entry) Traceln(args ...interface{}) {
entry.Logln(TraceLevel, args...)
}
func (entry *Entry) Debugln(args ...interface{}) {
entry.Logln(DebugLevel, args...)
}
func (entry *Entry) Infoln(args ...interface{}) {
if entry.Logger.level() >= InfoLevel {
entry.Info(entry.sprintlnn(args...))
}
entry.Logln(InfoLevel, args...)
}
func (entry *Entry) Println(args ...interface{}) {
@ -241,9 +377,7 @@ func (entry *Entry) Println(args ...interface{}) {
}
func (entry *Entry) Warnln(args ...interface{}) {
if entry.Logger.level() >= WarnLevel {
entry.Warn(entry.sprintlnn(args...))
}
entry.Logln(WarnLevel, args...)
}
func (entry *Entry) Warningln(args ...interface{}) {
@ -251,22 +385,16 @@ func (entry *Entry) Warningln(args ...interface{}) {
}
func (entry *Entry) Errorln(args ...interface{}) {
if entry.Logger.level() >= ErrorLevel {
entry.Error(entry.sprintlnn(args...))
}
entry.Logln(ErrorLevel, args...)
}
func (entry *Entry) Fatalln(args ...interface{}) {
if entry.Logger.level() >= FatalLevel {
entry.Fatal(entry.sprintlnn(args...))
}
Exit(1)
entry.Logln(FatalLevel, args...)
entry.Logger.Exit(1)
}
func (entry *Entry) Panicln(args ...interface{}) {
if entry.Logger.level() >= PanicLevel {
entry.Panic(entry.sprintlnn(args...))
}
entry.Logln(PanicLevel, args...)
}
// Sprintlnn => Sprint no newline. This is to get the behavior of how

View File

@ -1,77 +0,0 @@
package logrus
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestEntryWithError(t *testing.T) {
assert := assert.New(t)
defer func() {
ErrorKey = "error"
}()
err := fmt.Errorf("kaboom at layer %d", 4711)
assert.Equal(err, WithError(err).Data["error"])
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
assert.Equal(err, entry.WithError(err).Data["error"])
ErrorKey = "err"
assert.Equal(err, entry.WithError(err).Data["err"])
}
func TestEntryPanicln(t *testing.T) {
errBoom := fmt.Errorf("boom time")
defer func() {
p := recover()
assert.NotNil(t, p)
switch pVal := p.(type) {
case *Entry:
assert.Equal(t, "kaboom", pVal.Message)
assert.Equal(t, errBoom, pVal.Data["err"])
default:
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
}
}()
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
entry.WithField("err", errBoom).Panicln("kaboom")
}
func TestEntryPanicf(t *testing.T) {
errBoom := fmt.Errorf("boom again")
defer func() {
p := recover()
assert.NotNil(t, p)
switch pVal := p.(type) {
case *Entry:
assert.Equal(t, "kaboom true", pVal.Message)
assert.Equal(t, errBoom, pVal.Data["err"])
default:
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
}
}()
logger := New()
logger.Out = &bytes.Buffer{}
entry := NewEntry(logger)
entry.WithField("err", errBoom).Panicf("kaboom %v", true)
}

View File

@ -1,69 +0,0 @@
package logrus_test
import (
"github.com/sirupsen/logrus"
"os"
)
func Example_basic() {
var log = logrus.New()
log.Formatter = new(logrus.JSONFormatter)
log.Formatter = new(logrus.TextFormatter) //default
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
log.Level = logrus.DebugLevel
log.Out = os.Stdout
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
// if err == nil {
// log.Out = file
// } else {
// log.Info("Failed to log to file, using default stderr")
// }
defer func() {
err := recover()
if err != nil {
entry := err.(*logrus.Entry)
log.WithFields(logrus.Fields{
"omg": true,
"err_animal": entry.Data["animal"],
"err_size": entry.Data["size"],
"err_level": entry.Level,
"err_message": entry.Message,
"number": 100,
}).Error("The ice breaks!") // or use Fatal() to force the process to exit with a nonzero code
}
}()
log.WithFields(logrus.Fields{
"animal": "walrus",
"number": 8,
}).Debug("Started observing beach")
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"temperature": -4,
}).Debug("Temperature changes")
log.WithFields(logrus.Fields{
"animal": "orca",
"size": 9009,
}).Panic("It's over 9000!")
// Output:
// level=debug msg="Started observing beach" animal=walrus number=8
// level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
// level=warning msg="The group's number increased tremendously!" number=122 omg=true
// level=debug msg="Temperature changes" temperature=-4
// level=panic msg="It's over 9000!" animal=orca size=9009
// level=error msg="The ice breaks!" err_animal=orca err_level=panic err_message="It's over 9000!" err_size=9009 number=100 omg=true
}

View File

@ -1,35 +0,0 @@
package logrus_test
import (
"github.com/sirupsen/logrus"
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
"os"
)
func Example_hook() {
var log = logrus.New()
log.Formatter = new(logrus.TextFormatter) // default
log.Formatter.(*logrus.TextFormatter).DisableTimestamp = true // remove timestamp from test output
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
log.Out = os.Stdout
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"omg": true,
"number": 100,
}).Error("The ice breaks!")
// Output:
// level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
// level=warning msg="The group's number increased tremendously!" number=122 omg=true
// level=error msg="The ice breaks!" number=100 omg=true
}

View File

@ -1,7 +1,9 @@
package logrus
import (
"context"
"io"
"time"
)
var (
@ -15,37 +17,38 @@ 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)
}
// SetReportCaller sets whether the standard logger will include the calling
// method as a field.
func SetReportCaller(include bool) {
std.SetReportCaller(include)
}
// 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.
@ -53,6 +56,11 @@ func WithError(err error) *Entry {
return std.WithField(ErrorKey, err)
}
// WithContext creates an entry from the standard logger and adds a context to it.
func WithContext(ctx context.Context) *Entry {
return std.WithContext(ctx)
}
// WithField creates an entry from the standard logger and adds a field to
// it. If you want multiple fields, use `WithFields`.
//
@ -72,6 +80,20 @@ 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)
}
// Trace logs a message at level Trace on the standard logger.
func Trace(args ...interface{}) {
std.Trace(args...)
}
// Debug logs a message at level Debug on the standard logger.
func Debug(args ...interface{}) {
std.Debug(args...)
@ -107,11 +129,16 @@ 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...)
}
// Tracef logs a message at level Trace on the standard logger.
func Tracef(format string, args ...interface{}) {
std.Tracef(format, args...)
}
// Debugf logs a message at level Debug on the standard logger.
func Debugf(format string, args ...interface{}) {
std.Debugf(format, args...)
@ -147,11 +174,16 @@ 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...)
}
// Traceln logs a message at level Trace on the standard logger.
func Traceln(args ...interface{}) {
std.Traceln(args...)
}
// Debugln logs a message at level Debug on the standard logger.
func Debugln(args ...interface{}) {
std.Debugln(args...)
@ -187,7 +219,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...)
}

View File

@ -2,7 +2,16 @@ package logrus
import "time"
const defaultTimestampFormat = time.RFC3339
// Default key names for the default fields
const (
defaultTimestampFormat = time.RFC3339
FieldKeyMsg = "msg"
FieldKeyLevel = "level"
FieldKeyTime = "time"
FieldKeyLogrusError = "logrus_error"
FieldKeyFunc = "func"
FieldKeyFile = "file"
)
// The Formatter interface is used to implement a custom Formatter. It takes an
// `Entry`. It exposes all the fields, including the default ones:
@ -18,7 +27,7 @@ type Formatter interface {
Format(*Entry) ([]byte, error)
}
// This is to not silently overwrite `time`, `msg` and `level` fields when
// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when
// dumping it. If this code wasn't there doing:
//
// logrus.WithField("level", 1).Info("hello")
@ -30,16 +39,40 @@ 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, reportCaller bool) {
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)
}
logrusErrKey := fieldMap.resolve(FieldKeyLogrusError)
if l, ok := data[logrusErrKey]; ok {
data["fields."+logrusErrKey] = l
delete(data, logrusErrKey)
}
// If reportCaller is not set, 'func' will not conflict.
if reportCaller {
funcKey := fieldMap.resolve(FieldKeyFunc)
if l, ok := data[funcKey]; ok {
data["fields."+funcKey] = l
}
fileKey := fieldMap.resolve(FieldKeyFile)
if l, ok := data[fileKey]; ok {
data["fields."+fileKey] = l
}
}
}

View File

@ -1,101 +0,0 @@
package logrus
import (
"fmt"
"testing"
"time"
)
// smallFields is a small size data set for benchmarking
var smallFields = Fields{
"foo": "bar",
"baz": "qux",
"one": "two",
"three": "four",
}
// largeFields is a large size data set for benchmarking
var largeFields = Fields{
"foo": "bar",
"baz": "qux",
"one": "two",
"three": "four",
"five": "six",
"seven": "eight",
"nine": "ten",
"eleven": "twelve",
"thirteen": "fourteen",
"fifteen": "sixteen",
"seventeen": "eighteen",
"nineteen": "twenty",
"a": "b",
"c": "d",
"e": "f",
"g": "h",
"i": "j",
"k": "l",
"m": "n",
"o": "p",
"q": "r",
"s": "t",
"u": "v",
"w": "x",
"y": "z",
"this": "will",
"make": "thirty",
"entries": "yeah",
}
var errorFields = Fields{
"foo": fmt.Errorf("bar"),
"baz": fmt.Errorf("qux"),
}
func BenchmarkErrorTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, errorFields)
}
func BenchmarkSmallTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields)
}
func BenchmarkLargeTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields)
}
func BenchmarkSmallColoredTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields)
}
func BenchmarkLargeColoredTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields)
}
func BenchmarkSmallJSONFormatter(b *testing.B) {
doBenchmark(b, &JSONFormatter{}, smallFields)
}
func BenchmarkLargeJSONFormatter(b *testing.B) {
doBenchmark(b, &JSONFormatter{}, largeFields)
}
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
logger := New()
entry := &Entry{
Time: time.Time{},
Level: InfoLevel,
Message: "message",
Data: fields,
Logger: logger,
}
var d []byte
var err error
for i := 0; i < b.N; i++ {
d, err = formatter.Format(entry)
if err != nil {
b.Fatal(err)
}
b.SetBytes(int64(len(d)))
}
}

View File

@ -1,144 +0,0 @@
package logrus
import (
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
type TestHook struct {
Fired bool
}
func (hook *TestHook) Fire(entry *Entry) error {
hook.Fired = true
return nil
}
func (hook *TestHook) Levels() []Level {
return []Level{
DebugLevel,
InfoLevel,
WarnLevel,
ErrorLevel,
FatalLevel,
PanicLevel,
}
}
func TestHookFires(t *testing.T) {
hook := new(TestHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
assert.Equal(t, hook.Fired, false)
log.Print("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, true)
})
}
type ModifyHook struct {
}
func (hook *ModifyHook) Fire(entry *Entry) error {
entry.Data["wow"] = "whale"
return nil
}
func (hook *ModifyHook) Levels() []Level {
return []Level{
DebugLevel,
InfoLevel,
WarnLevel,
ErrorLevel,
FatalLevel,
PanicLevel,
}
}
func TestHookCanModifyEntry(t *testing.T) {
hook := new(ModifyHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.WithField("wow", "elephant").Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["wow"], "whale")
})
}
func TestCanFireMultipleHooks(t *testing.T) {
hook1 := new(ModifyHook)
hook2 := new(TestHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook1)
log.Hooks.Add(hook2)
log.WithField("wow", "elephant").Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["wow"], "whale")
assert.Equal(t, hook2.Fired, true)
})
}
type ErrorHook struct {
Fired bool
}
func (hook *ErrorHook) Fire(entry *Entry) error {
hook.Fired = true
return nil
}
func (hook *ErrorHook) Levels() []Level {
return []Level{
ErrorLevel,
}
}
func TestErrorHookShouldntFireOnInfo(t *testing.T) {
hook := new(ErrorHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.Info("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, false)
})
}
func TestErrorHookShouldFireOnError(t *testing.T) {
hook := new(ErrorHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.Error("test")
}, func(fields Fields) {
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
})
}

View File

@ -1,39 +0,0 @@
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
## Usage
```go
import (
"log/syslog"
"github.com/sirupsen/logrus"
lSyslog "github.com/sirupsen/logrus/hooks/syslog"
)
func main() {
log := logrus.New()
hook, err := lSyslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err == nil {
log.Hooks.Add(hook)
}
}
```
If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). Just assign empty string to the first two parameters of `NewSyslogHook`. It should look like the following.
```go
import (
"log/syslog"
"github.com/sirupsen/logrus"
lSyslog "github.com/sirupsen/logrus/hooks/syslog"
)
func main() {
log := logrus.New()
hook, err := lSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
if err == nil {
log.Hooks.Add(hook)
}
}
```

View File

@ -1,55 +0,0 @@
// +build !windows,!nacl,!plan9
package syslog
import (
"fmt"
"log/syslog"
"os"
"github.com/sirupsen/logrus"
)
// SyslogHook to send logs via syslog.
type SyslogHook struct {
Writer *syslog.Writer
SyslogNetwork string
SyslogRaddr string
}
// Creates a hook to be added to an instance of logger. This is called with
// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
// `if err == nil { log.Hooks.Add(hook) }`
func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
w, err := syslog.Dial(network, raddr, priority, tag)
return &SyslogHook{w, network, raddr}, err
}
func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
line, err := entry.String()
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
return err
}
switch entry.Level {
case logrus.PanicLevel:
return hook.Writer.Crit(line)
case logrus.FatalLevel:
return hook.Writer.Crit(line)
case logrus.ErrorLevel:
return hook.Writer.Err(line)
case logrus.WarnLevel:
return hook.Writer.Warning(line)
case logrus.InfoLevel:
return hook.Writer.Info(line)
case logrus.DebugLevel:
return hook.Writer.Debug(line)
default:
return nil
}
}
func (hook *SyslogHook) Levels() []logrus.Level {
return logrus.AllLevels
}

View File

@ -1,27 +0,0 @@
package syslog
import (
"log/syslog"
"testing"
"github.com/sirupsen/logrus"
)
func TestLocalhostAddAndPrint(t *testing.T) {
log := logrus.New()
hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err != nil {
t.Errorf("Unable to connect to local syslog.")
}
log.Hooks.Add(hook)
for _, level := range hook.Levels() {
if len(log.Hooks[level]) != 1 {
t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level]))
}
}
log.Info("Congratulations!")
}

View File

@ -1,95 +0,0 @@
// The Test package is used for testing logrus. It is here for backwards
// compatibility from when logrus' organization was upper-case. Please use
// lower-case logrus and the `null` package instead of this one.
package test
import (
"io/ioutil"
"sync"
"github.com/sirupsen/logrus"
)
// Hook is a hook designed for dealing with logs in test scenarios.
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
mu sync.RWMutex
}
// NewGlobal installs a test hook for the global logger.
func NewGlobal() *Hook {
hook := new(Hook)
logrus.AddHook(hook)
return hook
}
// NewLocal installs a test hook for a given local logger.
func NewLocal(logger *logrus.Logger) *Hook {
hook := new(Hook)
logger.Hooks.Add(hook)
return hook
}
// NewNullLogger creates a discarding logger and installs the test hook.
func NewNullLogger() (*logrus.Logger, *Hook) {
logger := logrus.New()
logger.Out = ioutil.Discard
return logger, NewLocal(logger)
}
func (t *Hook) Fire(e *logrus.Entry) error {
t.mu.Lock()
defer t.mu.Unlock()
t.Entries = append(t.Entries, e)
return nil
}
func (t *Hook) Levels() []logrus.Level {
return logrus.AllLevels
}
// LastEntry returns the last entry that was logged or nil.
func (t *Hook) LastEntry() *logrus.Entry {
t.mu.RLock()
defer t.mu.RUnlock()
i := len(t.Entries) - 1
if i < 0 {
return nil
}
// Make a copy, for safety
e := *t.Entries[i]
return &e
}
// AllEntries returns all entries that were logged.
func (t *Hook) AllEntries() []*logrus.Entry {
t.mu.RLock()
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 {
// Make a copy, for safety
e := *entry
entries[i] = &e
}
return entries
}
// Reset removes all Entries from this test hook.
func (t *Hook) Reset() {
t.mu.Lock()
defer t.mu.Unlock()
t.Entries = make([]*logrus.Entry, 0)
}

View File

@ -1,39 +0,0 @@
package test
import (
"testing"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
func TestAllHooks(t *testing.T) {
assert := assert.New(t)
logger, hook := NewNullLogger()
assert.Nil(hook.LastEntry())
assert.Equal(0, len(hook.Entries))
logger.Error("Hello error")
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
assert.Equal("Hello error", hook.LastEntry().Message)
assert.Equal(1, len(hook.Entries))
logger.Warn("Hello warning")
assert.Equal(logrus.WarnLevel, hook.LastEntry().Level)
assert.Equal("Hello warning", hook.LastEntry().Message)
assert.Equal(2, len(hook.Entries))
hook.Reset()
assert.Nil(hook.LastEntry())
assert.Equal(0, len(hook.Entries))
hook = NewGlobal()
logrus.Error("Hello error")
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
assert.Equal("Hello error", hook.LastEntry().Message)
assert.Equal(1, len(hook.Entries))
}

View File

@ -1,8 +1,10 @@
package logrus
import (
"bytes"
"encoding/json"
"fmt"
"runtime"
)
type fieldKey string
@ -10,13 +12,6 @@ type fieldKey string
// FieldMap allows customization of the key names for default fields.
type FieldMap map[fieldKey]string
// Default key names for the default fields
const (
FieldKeyMsg = "msg"
FieldKeyLevel = "level"
FieldKeyTime = "time"
)
func (f FieldMap) resolve(key fieldKey) string {
if k, ok := f[key]; ok {
return k
@ -33,6 +28,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{
@ -40,14 +38,24 @@ type JSONFormatter struct {
// FieldKeyTime: "@timestamp",
// FieldKeyLevel: "@level",
// FieldKeyMsg: "@message",
// FieldKeyFunc: "@caller",
// },
// }
FieldMap FieldMap
// CallerPrettyfier can be set by the user to modify the content
// of the function and file keys in the json data when ReportCaller is
// activated. If any of the returned value is the empty string the
// corresponding key will be removed from json fields.
CallerPrettyfier func(*runtime.Frame) (function string, file string)
// PrettyPrint will indent all json logs
PrettyPrint bool
}
// Format renders a single log entry
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
data := make(Fields, len(entry.Data)+3)
data := make(Fields, len(entry.Data)+4)
for k, v := range entry.Data {
switch v := v.(type) {
case error:
@ -58,22 +66,56 @@ 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, entry.HasCaller())
timestampFormat := f.TimestampFormat
if timestampFormat == "" {
timestampFormat = defaultTimestampFormat
}
if entry.err != "" {
data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err
}
if !f.DisableTimestamp {
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
}
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
serialized, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
if entry.HasCaller() {
funcVal := entry.Caller.Function
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
if f.CallerPrettyfier != nil {
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
}
return append(serialized, '\n'), nil
if funcVal != "" {
data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
}
if fileVal != "" {
data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
}
}
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 b.Bytes(), nil
}

View File

@ -1,199 +0,0 @@
package logrus
import (
"encoding/json"
"errors"
"strings"
"testing"
)
func TestErrorNotLost(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("error", errors.New("wild walrus")))
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)
}
if entry["error"] != "wild walrus" {
t.Fatal("Error field not set")
}
}
func TestErrorNotLostOnFieldNotNamedError(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("omg", errors.New("wild walrus")))
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)
}
if entry["omg"] != "wild walrus" {
t.Fatal("Error field not set")
}
}
func TestFieldClashWithTime(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("time", "right now!"))
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)
}
if entry["fields.time"] != "right now!" {
t.Fatal("fields.time not set to original time field")
}
if entry["time"] != "0001-01-01T00:00:00Z" {
t.Fatal("time field not set to current time, was: ", entry["time"])
}
}
func TestFieldClashWithMsg(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("msg", "something"))
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)
}
if entry["fields.msg"] != "something" {
t.Fatal("fields.msg not set to original msg field")
}
}
func TestFieldClashWithLevel(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("level", "something"))
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)
}
if entry["fields.level"] != "something" {
t.Fatal("fields.level not set to original level field")
}
}
func TestJSONEntryEndsWithNewline(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("level", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
if b[len(b)-1] != '\n' {
t.Fatal("Expected JSON log entry to end with a newline")
}
}
func TestJSONMessageKey(t *testing.T) {
formatter := &JSONFormatter{
FieldMap: FieldMap{
FieldKeyMsg: "message",
},
}
b, err := formatter.Format(&Entry{Message: "oh hai"})
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
s := string(b)
if !(strings.Contains(s, "message") && strings.Contains(s, "oh hai")) {
t.Fatal("Expected JSON to format message key")
}
}
func TestJSONLevelKey(t *testing.T) {
formatter := &JSONFormatter{
FieldMap: FieldMap{
FieldKeyLevel: "somelevel",
},
}
b, err := formatter.Format(WithField("level", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
s := string(b)
if !strings.Contains(s, "somelevel") {
t.Fatal("Expected JSON to format level key")
}
}
func TestJSONTimeKey(t *testing.T) {
formatter := &JSONFormatter{
FieldMap: FieldMap{
FieldKeyTime: "timeywimey",
},
}
b, err := formatter.Format(WithField("level", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
s := string(b)
if !strings.Contains(s, "timeywimey") {
t.Fatal("Expected JSON to format time key")
}
}
func TestJSONDisableTimestamp(t *testing.T) {
formatter := &JSONFormatter{
DisableTimestamp: true,
}
b, err := formatter.Format(WithField("level", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
s := string(b)
if strings.Contains(s, FieldKeyTime) {
t.Error("Did not prevent timestamp", s)
}
}
func TestJSONEnableTimestamp(t *testing.T) {
formatter := &JSONFormatter{}
b, err := formatter.Format(WithField("level", "something"))
if err != nil {
t.Fatal("Unable to format entry: ", err)
}
s := string(b)
if !strings.Contains(s, FieldKeyTime) {
t.Error("Timestamp not present", s)
}
}

View File

@ -1,16 +1,18 @@
package logrus
import (
"context"
"io"
"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
@ -23,6 +25,10 @@ type Logger struct {
// own that implements the `Formatter` interface, see the `README` or included
// formatters for examples.
Formatter Formatter
// Flag for whether to log caller info (off by default)
ReportCaller bool
// The logging level the logger should log at. This is typically (and defaults
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
// logged.
@ -31,8 +37,12 @@ type Logger struct {
mu MutexWrap
// Reusable empty entry
entryPool sync.Pool
// Function to exit the application, defaults to `os.Exit()`
ExitFunc exitFunc
}
type exitFunc func(int)
type MutexWrap struct {
lock sync.Mutex
disabled bool
@ -72,6 +82,8 @@ func New() *Logger {
Formatter: new(TextFormatter),
Hooks: make(LevelHooks),
Level: InfoLevel,
ExitFunc: os.Exit,
ReportCaller: false,
}
}
@ -84,11 +96,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,20 +125,38 @@ func (logger *Logger) WithError(err error) *Entry {
return entry.WithError(err)
}
func (logger *Logger) Debugf(format string, args ...interface{}) {
if logger.level() >= DebugLevel {
// Add a context to the log entry.
func (logger *Logger) WithContext(ctx context.Context) *Entry {
entry := logger.newEntry()
entry.Debugf(format, args...)
defer logger.releaseEntry(entry)
return entry.WithContext(ctx)
}
// 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) Logf(level Level, format string, args ...interface{}) {
if logger.IsLevelEnabled(level) {
entry := logger.newEntry()
entry.Logf(level, format, args...)
logger.releaseEntry(entry)
}
}
func (logger *Logger) Tracef(format string, args ...interface{}) {
logger.Logf(TraceLevel, format, args...)
}
func (logger *Logger) Debugf(format string, args ...interface{}) {
logger.Logf(DebugLevel, format, args...)
}
func (logger *Logger) Infof(format string, args ...interface{}) {
if logger.level() >= InfoLevel {
entry := logger.newEntry()
entry.Infof(format, args...)
logger.releaseEntry(entry)
}
logger.Logf(InfoLevel, format, args...)
}
func (logger *Logger) Printf(format string, args ...interface{}) {
@ -135,123 +166,91 @@ func (logger *Logger) Printf(format string, args ...interface{}) {
}
func (logger *Logger) Warnf(format string, args ...interface{}) {
if logger.level() >= WarnLevel {
entry := logger.newEntry()
entry.Warnf(format, args...)
logger.releaseEntry(entry)
}
logger.Logf(WarnLevel, format, args...)
}
func (logger *Logger) Warningf(format string, args ...interface{}) {
if logger.level() >= WarnLevel {
entry := logger.newEntry()
entry.Warnf(format, args...)
logger.releaseEntry(entry)
}
logger.Warnf(format, args...)
}
func (logger *Logger) Errorf(format string, args ...interface{}) {
if logger.level() >= ErrorLevel {
entry := logger.newEntry()
entry.Errorf(format, args...)
logger.releaseEntry(entry)
}
logger.Logf(ErrorLevel, format, args...)
}
func (logger *Logger) Fatalf(format string, args ...interface{}) {
if logger.level() >= FatalLevel {
entry := logger.newEntry()
entry.Fatalf(format, args...)
logger.releaseEntry(entry)
}
Exit(1)
logger.Logf(FatalLevel, format, args...)
logger.Exit(1)
}
func (logger *Logger) Panicf(format string, args ...interface{}) {
if logger.level() >= PanicLevel {
logger.Logf(PanicLevel, format, args...)
}
func (logger *Logger) Log(level Level, args ...interface{}) {
if logger.IsLevelEnabled(level) {
entry := logger.newEntry()
entry.Panicf(format, args...)
entry.Log(level, args...)
logger.releaseEntry(entry)
}
}
func (logger *Logger) Trace(args ...interface{}) {
logger.Log(TraceLevel, args...)
}
func (logger *Logger) Debug(args ...interface{}) {
if logger.level() >= DebugLevel {
entry := logger.newEntry()
entry.Debug(args...)
logger.releaseEntry(entry)
}
logger.Log(DebugLevel, args...)
}
func (logger *Logger) Info(args ...interface{}) {
if logger.level() >= InfoLevel {
entry := logger.newEntry()
entry.Info(args...)
logger.releaseEntry(entry)
}
logger.Log(InfoLevel, args...)
}
func (logger *Logger) Print(args ...interface{}) {
entry := logger.newEntry()
entry.Info(args...)
entry.Print(args...)
logger.releaseEntry(entry)
}
func (logger *Logger) Warn(args ...interface{}) {
if logger.level() >= WarnLevel {
entry := logger.newEntry()
entry.Warn(args...)
logger.releaseEntry(entry)
}
logger.Log(WarnLevel, args...)
}
func (logger *Logger) Warning(args ...interface{}) {
if logger.level() >= WarnLevel {
entry := logger.newEntry()
entry.Warn(args...)
logger.releaseEntry(entry)
}
logger.Warn(args...)
}
func (logger *Logger) Error(args ...interface{}) {
if logger.level() >= ErrorLevel {
entry := logger.newEntry()
entry.Error(args...)
logger.releaseEntry(entry)
}
logger.Log(ErrorLevel, args...)
}
func (logger *Logger) Fatal(args ...interface{}) {
if logger.level() >= FatalLevel {
entry := logger.newEntry()
entry.Fatal(args...)
logger.releaseEntry(entry)
}
Exit(1)
logger.Log(FatalLevel, args...)
logger.Exit(1)
}
func (logger *Logger) Panic(args ...interface{}) {
if logger.level() >= PanicLevel {
logger.Log(PanicLevel, args...)
}
func (logger *Logger) Logln(level Level, args ...interface{}) {
if logger.IsLevelEnabled(level) {
entry := logger.newEntry()
entry.Panic(args...)
entry.Logln(level, args...)
logger.releaseEntry(entry)
}
}
func (logger *Logger) Traceln(args ...interface{}) {
logger.Logln(TraceLevel, args...)
}
func (logger *Logger) Debugln(args ...interface{}) {
if logger.level() >= DebugLevel {
entry := logger.newEntry()
entry.Debugln(args...)
logger.releaseEntry(entry)
}
logger.Logln(DebugLevel, args...)
}
func (logger *Logger) Infoln(args ...interface{}) {
if logger.level() >= InfoLevel {
entry := logger.newEntry()
entry.Infoln(args...)
logger.releaseEntry(entry)
}
logger.Logln(InfoLevel, args...)
}
func (logger *Logger) Println(args ...interface{}) {
@ -261,44 +260,32 @@ func (logger *Logger) Println(args ...interface{}) {
}
func (logger *Logger) Warnln(args ...interface{}) {
if logger.level() >= WarnLevel {
entry := logger.newEntry()
entry.Warnln(args...)
logger.releaseEntry(entry)
}
logger.Logln(WarnLevel, args...)
}
func (logger *Logger) Warningln(args ...interface{}) {
if logger.level() >= WarnLevel {
entry := logger.newEntry()
entry.Warnln(args...)
logger.releaseEntry(entry)
}
logger.Warnln(args...)
}
func (logger *Logger) Errorln(args ...interface{}) {
if logger.level() >= ErrorLevel {
entry := logger.newEntry()
entry.Errorln(args...)
logger.releaseEntry(entry)
}
logger.Logln(ErrorLevel, args...)
}
func (logger *Logger) Fatalln(args ...interface{}) {
if logger.level() >= FatalLevel {
entry := logger.newEntry()
entry.Fatalln(args...)
logger.releaseEntry(entry)
}
Exit(1)
logger.Logln(FatalLevel, args...)
logger.Exit(1)
}
func (logger *Logger) Panicln(args ...interface{}) {
if logger.level() >= PanicLevel {
entry := logger.newEntry()
entry.Panicln(args...)
logger.releaseEntry(entry)
logger.Logln(PanicLevel, args...)
}
func (logger *Logger) Exit(code int) {
runHandlers()
if logger.ExitFunc == nil {
logger.ExitFunc = os.Exit
}
logger.ExitFunc(code)
}
//When file is opened with appending mode, it's safe to
@ -312,12 +299,53 @@ 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
}
func (logger *Logger) SetReportCaller(reportCaller bool) {
logger.mu.Lock()
defer logger.mu.Unlock()
logger.ReportCaller = reportCaller
}
// 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
}

View File

@ -1,61 +0,0 @@
package logrus
import (
"os"
"testing"
)
// smallFields is a small size data set for benchmarking
var loggerFields = Fields{
"foo": "bar",
"baz": "qux",
"one": "two",
"three": "four",
}
func BenchmarkDummyLogger(b *testing.B) {
nullf, err := os.OpenFile("/dev/null", os.O_WRONLY, 0666)
if err != nil {
b.Fatalf("%v", err)
}
defer nullf.Close()
doLoggerBenchmark(b, nullf, &TextFormatter{DisableColors: true}, smallFields)
}
func BenchmarkDummyLoggerNoLock(b *testing.B) {
nullf, err := os.OpenFile("/dev/null", os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
b.Fatalf("%v", err)
}
defer nullf.Close()
doLoggerBenchmarkNoLock(b, nullf, &TextFormatter{DisableColors: true}, smallFields)
}
func doLoggerBenchmark(b *testing.B, out *os.File, formatter Formatter, fields Fields) {
logger := Logger{
Out: out,
Level: InfoLevel,
Formatter: formatter,
}
entry := logger.WithFields(fields)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
entry.Info("aaa")
}
})
}
func doLoggerBenchmarkNoLock(b *testing.B, out *os.File, formatter Formatter, fields Fields) {
logger := Logger{
Out: out,
Level: InfoLevel,
Formatter: formatter,
}
logger.SetNoLock()
entry := logger.WithFields(fields)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
entry.Info("aaa")
}
})
}

View File

@ -14,22 +14,11 @@ type Level uint32
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
func (level Level) String() string {
switch level {
case DebugLevel:
return "debug"
case InfoLevel:
return "info"
case WarnLevel:
return "warning"
case ErrorLevel:
return "error"
case FatalLevel:
return "fatal"
case PanicLevel:
return "panic"
}
if b, err := level.MarshalText(); err == nil {
return string(b)
} else {
return "unknown"
}
}
// ParseLevel takes a string level and returns the Logrus log level constant.
@ -47,12 +36,47 @@ func ParseLevel(lvl string) (Level, error) {
return InfoLevel, nil
case "debug":
return DebugLevel, nil
case "trace":
return TraceLevel, nil
}
var l Level
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (level *Level) UnmarshalText(text []byte) error {
l, err := ParseLevel(string(text))
if err != nil {
return err
}
*level = Level(l)
return nil
}
func (level Level) MarshalText() ([]byte, error) {
switch level {
case TraceLevel:
return []byte("trace"), nil
case DebugLevel:
return []byte("debug"), nil
case InfoLevel:
return []byte("info"), nil
case WarnLevel:
return []byte("warning"), nil
case ErrorLevel:
return []byte("error"), nil
case FatalLevel:
return []byte("fatal"), nil
case PanicLevel:
return []byte("panic"), nil
}
return nil, fmt.Errorf("not a valid logrus level %d", level)
}
// A constant exposing all logging levels
var AllLevels = []Level{
PanicLevel,
@ -61,6 +85,7 @@ var AllLevels = []Level{
WarnLevel,
InfoLevel,
DebugLevel,
TraceLevel,
}
// These are the different logging levels. You can set the logging level to log
@ -69,7 +94,7 @@ const (
// PanicLevel level, highest level of severity. Logs and then calls panic with the
// message passed to Debug, Info, ...
PanicLevel Level = iota
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
// logging level is set to Panic.
FatalLevel
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
@ -82,6 +107,8 @@ const (
InfoLevel
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
DebugLevel
// TraceLevel level. Designates finer-grained informational events than the Debug.
TraceLevel
)
// Won't compile if StdLogger can't be realized by a log.Logger
@ -140,4 +167,20 @@ type FieldLogger interface {
Errorln(args ...interface{})
Fatalln(args ...interface{})
Panicln(args ...interface{})
// IsDebugEnabled() bool
// IsInfoEnabled() bool
// IsWarnEnabled() bool
// IsErrorEnabled() bool
// IsFatalEnabled() bool
// IsPanicEnabled() bool
}
// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is
// here for consistancy. Do not use. Use Logger or Entry instead.
type Ext1FieldLogger interface {
FieldLogger
Tracef(format string, args ...interface{})
Trace(args ...interface{})
Traceln(args ...interface{})
}

View File

@ -1,386 +0,0 @@
package logrus
import (
"bytes"
"encoding/json"
"strconv"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) {
var buffer bytes.Buffer
var fields Fields
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
log(logger)
err := json.Unmarshal(buffer.Bytes(), &fields)
assert.Nil(t, err)
assertions(fields)
}
func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) {
var buffer bytes.Buffer
logger := New()
logger.Out = &buffer
logger.Formatter = &TextFormatter{
DisableColors: true,
}
log(logger)
fields := make(map[string]string)
for _, kv := range strings.Split(buffer.String(), " ") {
if !strings.Contains(kv, "=") {
continue
}
kvArr := strings.Split(kv, "=")
key := strings.TrimSpace(kvArr[0])
val := kvArr[1]
if kvArr[1][0] == '"' {
var err error
val, err = strconv.Unquote(val)
assert.NoError(t, err)
}
fields[key] = val
}
assertions(fields)
}
func TestPrint(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["level"], "info")
})
}
func TestInfo(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["level"], "info")
})
}
func TestWarn(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Warn("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["level"], "warning")
})
}
func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln("test", "test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test test")
})
}
func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln("test", 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test 10")
})
}
func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln(10, 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "10 10")
})
}
func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln(10, 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "10 10")
})
}
func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Info("test", 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test10")
})
}
func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Info("test", "test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "testtest")
})
}
func TestWithFieldsShouldAllowAssignments(t *testing.T) {
var buffer bytes.Buffer
var fields Fields
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
localLog := logger.WithFields(Fields{
"key1": "value1",
})
localLog.WithField("key2", "value2").Info("test")
err := json.Unmarshal(buffer.Bytes(), &fields)
assert.Nil(t, err)
assert.Equal(t, "value2", fields["key2"])
assert.Equal(t, "value1", fields["key1"])
buffer = bytes.Buffer{}
fields = Fields{}
localLog.Info("test")
err = json.Unmarshal(buffer.Bytes(), &fields)
assert.Nil(t, err)
_, ok := fields["key2"]
assert.Equal(t, false, ok)
assert.Equal(t, "value1", fields["key1"])
}
func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("msg", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
})
}
func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("msg", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["fields.msg"], "hello")
})
}
func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("time", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["fields.time"], "hello")
})
}
func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("level", 1).Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["level"], "info")
assert.Equal(t, fields["fields.level"], 1.0) // JSON has floats only
})
}
func TestDefaultFieldsAreNotPrefixed(t *testing.T) {
LogAndAssertText(t, func(log *Logger) {
ll := log.WithField("herp", "derp")
ll.Info("hello")
ll.Info("bye")
}, func(fields map[string]string) {
for _, fieldName := range []string{"fields.level", "fields.time", "fields.msg"} {
if _, ok := fields[fieldName]; ok {
t.Fatalf("should not have prefixed %q: %v", fieldName, fields)
}
}
})
}
func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
var buffer bytes.Buffer
var fields Fields
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
llog := logger.WithField("context", "eating raw fish")
llog.Info("looks delicious")
err := json.Unmarshal(buffer.Bytes(), &fields)
assert.NoError(t, err, "should have decoded first message")
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
assert.Equal(t, fields["msg"], "looks delicious")
assert.Equal(t, fields["context"], "eating raw fish")
buffer.Reset()
llog.Warn("omg it is!")
err = json.Unmarshal(buffer.Bytes(), &fields)
assert.NoError(t, err, "should have decoded second message")
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
assert.Equal(t, fields["msg"], "omg it is!")
assert.Equal(t, fields["context"], "eating raw fish")
assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry")
}
func TestConvertLevelToString(t *testing.T) {
assert.Equal(t, "debug", DebugLevel.String())
assert.Equal(t, "info", InfoLevel.String())
assert.Equal(t, "warning", WarnLevel.String())
assert.Equal(t, "error", ErrorLevel.String())
assert.Equal(t, "fatal", FatalLevel.String())
assert.Equal(t, "panic", PanicLevel.String())
}
func TestParseLevel(t *testing.T) {
l, err := ParseLevel("panic")
assert.Nil(t, err)
assert.Equal(t, PanicLevel, l)
l, err = ParseLevel("PANIC")
assert.Nil(t, err)
assert.Equal(t, PanicLevel, l)
l, err = ParseLevel("fatal")
assert.Nil(t, err)
assert.Equal(t, FatalLevel, l)
l, err = ParseLevel("FATAL")
assert.Nil(t, err)
assert.Equal(t, FatalLevel, l)
l, err = ParseLevel("error")
assert.Nil(t, err)
assert.Equal(t, ErrorLevel, l)
l, err = ParseLevel("ERROR")
assert.Nil(t, err)
assert.Equal(t, ErrorLevel, l)
l, err = ParseLevel("warn")
assert.Nil(t, err)
assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("WARN")
assert.Nil(t, err)
assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("warning")
assert.Nil(t, err)
assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("WARNING")
assert.Nil(t, err)
assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("info")
assert.Nil(t, err)
assert.Equal(t, InfoLevel, l)
l, err = ParseLevel("INFO")
assert.Nil(t, err)
assert.Equal(t, InfoLevel, l)
l, err = ParseLevel("debug")
assert.Nil(t, err)
assert.Equal(t, DebugLevel, l)
l, err = ParseLevel("DEBUG")
assert.Nil(t, err)
assert.Equal(t, DebugLevel, l)
l, err = ParseLevel("invalid")
assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
}
func TestGetSetLevelRace(t *testing.T) {
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
if i%2 == 0 {
SetLevel(InfoLevel)
} else {
GetLevel()
}
}(i)
}
wg.Wait()
}
func TestLoggingRace(t *testing.T) {
logger := New()
var wg sync.WaitGroup
wg.Add(100)
for i := 0; i < 100; i++ {
go func() {
logger.Info("info")
wg.Done()
}()
}
wg.Wait()
}
// Compile test
func TestLogrusInterface(t *testing.T) {
var buffer bytes.Buffer
fn := func(l FieldLogger) {
b := l.WithField("key", "value")
b.Debug("Test")
}
// test logger
logger := New()
logger.Out = &buffer
fn(logger)
// test Entry
e := logger.WithField("another", "value")
fn(e)
}
// Implements io.Writer using channels for synchronization, so we can wait on
// the Entry.Writer goroutine to write in a non-racey way. This does assume that
// there is a single call to Logger.Out for each message.
type channelWriter chan []byte
func (cw channelWriter) Write(p []byte) (int, error) {
cw <- p
return len(p), nil
}
func TestEntryWriter(t *testing.T) {
cw := channelWriter(make(chan []byte, 1))
log := New()
log.Out = cw
log.Formatter = new(JSONFormatter)
log.WithField("foo", "bar").WriterLevel(WarnLevel).Write([]byte("hello\n"))
bs := <-cw
var fields Fields
err := json.Unmarshal(bs, &fields)
assert.Nil(t, err)
assert.Equal(t, fields["foo"], "bar")
assert.Equal(t, fields["level"], "warning")
}

View File

@ -1,10 +0,0 @@
// +build darwin freebsd openbsd netbsd dragonfly
// +build !appengine
package logrus
import "golang.org/x/sys/unix"
const ioctlReadTermios = unix.TIOCGETA
type Termios unix.Termios

View File

@ -0,0 +1,13 @@
// +build darwin dragonfly freebsd netbsd openbsd
package logrus
import "golang.org/x/sys/unix"
const ioctlReadTermios = unix.TIOCGETA
func isTerminal(fd int) bool {
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
return err == nil
}

View File

@ -0,0 +1,11 @@
// +build js nacl plan9
package logrus
import (
"io"
)
func checkIfTerminal(w io.Writer) bool {
return false
}

View File

@ -1,18 +1,16 @@
// +build !appengine
// +build !appengine,!js,!windows,!nacl,!plan9
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()))
return isTerminal(int(v.Fd()))
default:
return false
}

View File

@ -0,0 +1,11 @@
package logrus
import (
"golang.org/x/sys/unix"
)
// IsTerminal returns true if the given file descriptor is a terminal.
func isTerminal(fd int) bool {
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
return err == nil
}

View File

@ -0,0 +1,13 @@
// +build linux aix
package logrus
import "golang.org/x/sys/unix"
const ioctlReadTermios = unix.TCGETS
func isTerminal(fd int) bool {
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
return err == nil
}

View File

@ -0,0 +1,34 @@
// +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)
}
}
func checkIfTerminal(w io.Writer) bool {
var ret bool
switch v := w.(type) {
case *os.File:
var mode uint32
err := syscall.GetConsoleMode(syscall.Handle(v.Fd()), &mode)
ret = (err == nil)
default:
ret = false
}
if ret {
initTerminal(w)
}
return ret
}

View File

@ -1,14 +0,0 @@
// Based on ssh/terminal:
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !appengine
package logrus
import "golang.org/x/sys/unix"
const ioctlReadTermios = unix.TCGETS
type Termios unix.Termios

View File

@ -3,6 +3,8 @@ package logrus
import (
"bytes"
"fmt"
"os"
"runtime"
"sort"
"strings"
"sync"
@ -10,17 +12,13 @@ import (
)
const (
nocolor = 0
red = 31
green = 32
yellow = 33
blue = 36
gray = 37
)
var (
baseTimestamp time.Time
)
var baseTimestamp time.Time
func init() {
baseTimestamp = time.Now()
@ -34,6 +32,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
@ -50,13 +51,34 @@ 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
// CallerPrettyfier can be set by the user to modify the content
// of the function and file keys in the data when ReportCaller is
// activated. If any of the returned value is the empty string the
// corresponding key will be removed from fields.
CallerPrettyfier func(*runtime.Frame) (function string, file string)
terminalInitOnce sync.Once
}
func (f *TextFormatter) init(entry *Entry) {
@ -65,45 +87,115 @@ func (f *TextFormatter) init(entry *Entry) {
}
}
func (f *TextFormatter) isColored() bool {
isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
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
keys := make([]string, 0, len(entry.Data))
for k := range entry.Data {
data := make(Fields)
for k, v := range entry.Data {
data[k] = v
}
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
if !f.DisableSorting {
sort.Strings(keys)
var funcVal, fileVal string
fixedKeys := make([]string, 0, 4+len(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 entry.err != "" {
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
}
if entry.HasCaller() {
if f.CallerPrettyfier != nil {
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
} else {
funcVal = entry.Caller.Function
fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
}
if funcVal != "" {
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc))
}
if fileVal != "" {
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile))
}
}
if !f.DisableSorting {
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 {
f.printColored(b, entry, keys, timestampFormat)
if f.isColored() {
f.printColored(b, entry, keys, data, timestampFormat)
} else {
if !f.DisableTimestamp {
f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
for _, key := range fixedKeys {
var value interface{}
switch {
case key == f.FieldMap.resolve(FieldKeyTime):
value = entry.Time.Format(timestampFormat)
case key == f.FieldMap.resolve(FieldKeyLevel):
value = entry.Level.String()
case key == f.FieldMap.resolve(FieldKeyMsg):
value = entry.Message
case key == f.FieldMap.resolve(FieldKeyLogrusError):
value = entry.err
case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
value = funcVal
case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
value = fileVal
default:
value = data[key]
}
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])
f.appendKeyValue(b, key, value)
}
}
@ -111,10 +203,10 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
return b.Bytes(), nil
}
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) {
var levelColor int
switch entry.Level {
case DebugLevel:
case DebugLevel, TraceLevel:
levelColor = gray
case WarnLevel:
levelColor = yellow
@ -124,17 +216,42 @@ 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")
caller := ""
if entry.HasCaller() {
funcVal := fmt.Sprintf("%s()", entry.Caller.Function)
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
if f.CallerPrettyfier != nil {
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
}
if fileVal == "" {
caller = funcVal
} else if funcVal == "" {
caller = fileVal
} else {
caller = fileVal + " " + funcVal
}
}
if f.DisableTimestamp {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message)
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
} else if !f.FullTimestamp {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message)
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
} else {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
}
for _, k := range keys {
v := entry.Data[k]
v := data[k]
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
f.appendValue(b, v)
}

View File

@ -1,141 +0,0 @@
package logrus
import (
"bytes"
"errors"
"fmt"
"strings"
"testing"
"time"
)
func TestFormatting(t *testing.T) {
tf := &TextFormatter{DisableColors: true}
testCases := []struct {
value string
expected string
}{
{`foo`, "time=\"0001-01-01T00:00:00Z\" level=panic test=foo\n"},
}
for _, tc := range testCases {
b, _ := tf.Format(WithField("test", tc.value))
if string(b) != tc.expected {
t.Errorf("formatting expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
}
}
}
func TestQuoting(t *testing.T) {
tf := &TextFormatter{DisableColors: true}
checkQuoting := func(q bool, value interface{}) {
b, _ := tf.Format(WithField("test", value))
idx := bytes.Index(b, ([]byte)("test="))
cont := bytes.Contains(b[idx+5:], []byte("\""))
if cont != q {
if q {
t.Errorf("quoting expected for: %#v", value)
} else {
t.Errorf("quoting not expected for: %#v", value)
}
}
}
checkQuoting(false, "")
checkQuoting(false, "abcd")
checkQuoting(false, "v1.0")
checkQuoting(false, "1234567890")
checkQuoting(false, "/foobar")
checkQuoting(false, "foo_bar")
checkQuoting(false, "foo@bar")
checkQuoting(false, "foobar^")
checkQuoting(false, "+/-_^@f.oobar")
checkQuoting(true, "foobar$")
checkQuoting(true, "&foobar")
checkQuoting(true, "x y")
checkQuoting(true, "x,y")
checkQuoting(false, errors.New("invalid"))
checkQuoting(true, errors.New("invalid argument"))
// Test for quoting empty fields.
tf.QuoteEmptyFields = true
checkQuoting(true, "")
checkQuoting(false, "abcd")
checkQuoting(true, errors.New("invalid argument"))
}
func TestEscaping(t *testing.T) {
tf := &TextFormatter{DisableColors: true}
testCases := []struct {
value string
expected string
}{
{`ba"r`, `ba\"r`},
{`ba'r`, `ba'r`},
}
for _, tc := range testCases {
b, _ := tf.Format(WithField("test", tc.value))
if !bytes.Contains(b, []byte(tc.expected)) {
t.Errorf("escaping expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
}
}
}
func TestEscaping_Interface(t *testing.T) {
tf := &TextFormatter{DisableColors: true}
ts := time.Now()
testCases := []struct {
value interface{}
expected string
}{
{ts, fmt.Sprintf("\"%s\"", ts.String())},
{errors.New("error: something went wrong"), "\"error: something went wrong\""},
}
for _, tc := range testCases {
b, _ := tf.Format(WithField("test", tc.value))
if !bytes.Contains(b, []byte(tc.expected)) {
t.Errorf("escaping expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
}
}
}
func TestTimestampFormat(t *testing.T) {
checkTimeStr := func(format string) {
customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format}
customStr, _ := customFormatter.Format(WithField("test", "test"))
timeStart := bytes.Index(customStr, ([]byte)("time="))
timeEnd := bytes.Index(customStr, ([]byte)("level="))
timeStr := customStr[timeStart+5+len("\"") : timeEnd-1-len("\"")]
if format == "" {
format = time.RFC3339
}
_, e := time.Parse(format, (string)(timeStr))
if e != nil {
t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e)
}
}
checkTimeStr("2006-01-02T15:04:05.000000000Z07:00")
checkTimeStr("Mon Jan _2 15:04:05 2006")
checkTimeStr("")
}
func TestDisableTimestampWithColoredOutput(t *testing.T) {
tf := &TextFormatter{DisableTimestamp: true, ForceColors: true}
b, _ := tf.Format(WithField("test", "test"))
if strings.Contains(string(b), "[0000]") {
t.Error("timestamp not expected when DisableTimestamp is true")
}
}
// TODO add tests for sorting etc., this requires a parser for the text
// formatter output.

View File

@ -24,6 +24,8 @@ func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
var printFunc func(args ...interface{})
switch level {
case TraceLevel:
printFunc = entry.Trace
case DebugLevel:
printFunc = entry.Debug
case InfoLevel:

View File

@ -1,2 +0,0 @@
.idea/*

View File

@ -1,21 +0,0 @@
sudo: false
language: go
go:
- 1.7.3
- 1.8.1
- tip
matrix:
allow_failures:
- go: tip
install:
- go get github.com/golang/lint/golint
- export PATH=$GOPATH/bin:$PATH
- go install ./...
script:
- verify/all.sh -v
- go test ./...

View File

@ -1,296 +0,0 @@
[![Build Status](https://travis-ci.org/spf13/pflag.svg?branch=master)](https://travis-ci.org/spf13/pflag)
[![Go Report Card](https://goreportcard.com/badge/github.com/spf13/pflag)](https://goreportcard.com/report/github.com/spf13/pflag)
[![GoDoc](https://godoc.org/github.com/spf13/pflag?status.svg)](https://godoc.org/github.com/spf13/pflag)
## Description
pflag is a drop-in replacement for Go's flag package, implementing
POSIX/GNU-style --flags.
pflag is compatible with the [GNU extensions to the POSIX recommendations
for command-line options][1]. For a more precise description, see the
"Command-line flag syntax" section below.
[1]: http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
pflag is available under the same style of BSD license as the Go language,
which can be found in the LICENSE file.
## Installation
pflag is available using the standard `go get` command.
Install by running:
go get github.com/spf13/pflag
Run tests by running:
go test github.com/spf13/pflag
## Usage
pflag is a drop-in replacement of Go's native flag package. If you import
pflag under the name "flag" then all code should continue to function
with no changes.
``` go
import flag "github.com/spf13/pflag"
```
There is one exception to this: if you directly instantiate the Flag struct
there is one more field "Shorthand" that you will need to set.
Most code never instantiates this struct directly, and instead uses
functions such as String(), BoolVar(), and Var(), and is therefore
unaffected.
Define flags using flag.String(), Bool(), Int(), etc.
This declares an integer flag, -flagname, stored in the pointer ip, with type *int.
``` go
var ip *int = flag.Int("flagname", 1234, "help message for flagname")
```
If you like, you can bind the flag to a variable using the Var() functions.
``` go
var flagvar int
func init() {
flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
}
```
Or you can create custom flags that satisfy the Value interface (with
pointer receivers) and couple them to flag parsing by
``` go
flag.Var(&flagVal, "name", "help message for flagname")
```
For such flags, the default value is just the initial value of the variable.
After all flags are defined, call
``` go
flag.Parse()
```
to parse the command line into the defined flags.
Flags may then be used directly. If you're using the flags themselves,
they are all pointers; if you bind to variables, they're values.
``` go
fmt.Println("ip has value ", *ip)
fmt.Println("flagvar has value ", flagvar)
```
There are helpers function to get values later if you have the FlagSet but
it was difficult to keep up with all of the flag pointers in your code.
If you have a pflag.FlagSet with a flag called 'flagname' of type int you
can use GetInt() to get the int value. But notice that 'flagname' must exist
and it must be an int. GetString("flagname") will fail.
``` go
i, err := flagset.GetInt("flagname")
```
After parsing, the arguments after the flag are available as the
slice flag.Args() or individually as flag.Arg(i).
The arguments are indexed from 0 through flag.NArg()-1.
The pflag package also defines some new functions that are not in flag,
that give one-letter shorthands for flags. You can use these by appending
'P' to the name of any function that defines a flag.
``` go
var ip = flag.IntP("flagname", "f", 1234, "help message")
var flagvar bool
func init() {
flag.BoolVarP(&flagvar, "boolname", "b", true, "help message")
}
flag.VarP(&flagVal, "varname", "v", "help message")
```
Shorthand letters can be used with single dashes on the command line.
Boolean shorthand flags can be combined with other shorthand flags.
The default set of command-line flags is controlled by
top-level functions. The FlagSet type allows one to define
independent sets of flags, such as to implement subcommands
in a command-line interface. The methods of FlagSet are
analogous to the top-level functions for the command-line
flag set.
## Setting no option default values for flags
After you create a flag it is possible to set the pflag.NoOptDefVal for
the given flag. Doing this changes the meaning of the flag slightly. If
a flag has a NoOptDefVal and the flag is set on the command line without
an option the flag will be set to the NoOptDefVal. For example given:
``` go
var ip = flag.IntP("flagname", "f", 1234, "help message")
flag.Lookup("flagname").NoOptDefVal = "4321"
```
Would result in something like
| Parsed Arguments | Resulting Value |
| ------------- | ------------- |
| --flagname=1357 | ip=1357 |
| --flagname | ip=4321 |
| [nothing] | ip=1234 |
## Command line flag syntax
```
--flag // boolean flags, or flags with no option default values
--flag x // only on flags without a default value
--flag=x
```
Unlike the flag package, a single dash before an option means something
different than a double dash. Single dashes signify a series of shorthand
letters for flags. All but the last shorthand letter must be boolean flags
or a flag with a default value
```
// boolean or flags where the 'no option default value' is set
-f
-f=true
-abc
but
-b true is INVALID
// non-boolean and flags without a 'no option default value'
-n 1234
-n=1234
-n1234
// mixed
-abcs "hello"
-absd="hello"
-abcs1234
```
Flag parsing stops after the terminator "--". Unlike the flag package,
flags can be interspersed with arguments anywhere on the command line
before this terminator.
Integer flags accept 1234, 0664, 0x1234 and may be negative.
Boolean flags (in their long form) accept 1, 0, t, f, true, false,
TRUE, FALSE, True, False.
Duration flags accept any input valid for time.ParseDuration.
## Mutating or "Normalizing" Flag names
It is possible to set a custom flag name 'normalization function.' It allows flag names to be mutated both when created in the code and when used on the command line to some 'normalized' form. The 'normalized' form is used for comparison. Two examples of using the custom normalization func follow.
**Example #1**: You want -, _, and . in flags to compare the same. aka --my-flag == --my_flag == --my.flag
``` go
func wordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
from := []string{"-", "_"}
to := "."
for _, sep := range from {
name = strings.Replace(name, sep, to, -1)
}
return pflag.NormalizedName(name)
}
myFlagSet.SetNormalizeFunc(wordSepNormalizeFunc)
```
**Example #2**: You want to alias two flags. aka --old-flag-name == --new-flag-name
``` go
func aliasNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
switch name {
case "old-flag-name":
name = "new-flag-name"
break
}
return pflag.NormalizedName(name)
}
myFlagSet.SetNormalizeFunc(aliasNormalizeFunc)
```
## Deprecating a flag or its shorthand
It is possible to deprecate a flag, or just its shorthand. Deprecating a flag/shorthand hides it from help text and prints a usage message when the deprecated flag/shorthand is used.
**Example #1**: You want to deprecate a flag named "badflag" as well as inform the users what flag they should use instead.
```go
// deprecate a flag by specifying its name and a usage message
flags.MarkDeprecated("badflag", "please use --good-flag instead")
```
This hides "badflag" from help text, and prints `Flag --badflag has been deprecated, please use --good-flag instead` when "badflag" is used.
**Example #2**: You want to keep a flag name "noshorthandflag" but deprecate its shortname "n".
```go
// deprecate a flag shorthand by specifying its flag name and a usage message
flags.MarkShorthandDeprecated("noshorthandflag", "please use --noshorthandflag only")
```
This hides the shortname "n" from help text, and prints `Flag shorthand -n has been deprecated, please use --noshorthandflag only` when the shorthand "n" is used.
Note that usage message is essential here, and it should not be empty.
## Hidden flags
It is possible to mark a flag as hidden, meaning it will still function as normal, however will not show up in usage/help text.
**Example**: You have a flag named "secretFlag" that you need for internal use only and don't want it showing up in help text, or for its usage text to be available.
```go
// hide a flag by specifying its name
flags.MarkHidden("secretFlag")
```
## Disable sorting of flags
`pflag` allows you to disable sorting of flags for help and usage message.
**Example**:
```go
flags.BoolP("verbose", "v", false, "verbose output")
flags.String("coolflag", "yeaah", "it's really cool flag")
flags.Int("usefulflag", 777, "sometimes it's very useful")
flags.SortFlags = false
flags.PrintDefaults()
```
**Output**:
```
-v, --verbose verbose output
--coolflag string it's really cool flag (default "yeaah")
--usefulflag int sometimes it's very useful (default 777)
```
## Supporting Go flags when using pflag
In order to support flags defined using Go's `flag` package, they must be added to the `pflag` flagset. This is usually necessary
to support flags defined by third-party dependencies (e.g. `golang/glog`).
**Example**: You want to add the Go flags to the `CommandLine` flagset
```go
import (
goflag "flag"
flag "github.com/spf13/pflag"
)
var ip *int = flag.Int("flagname", 1234, "help message for flagname")
func main() {
flag.CommandLine.AddGoFlagSet(goflag.CommandLine)
flag.Parse()
}
```
## More info
You can see the full reference documentation of the pflag package
[at godoc.org][3], or through go's standard documentation system by
running `godoc -http=:6060` and browsing to
[http://localhost:6060/pkg/github.com/spf13/pflag][2] after
installation.
[2]: http://localhost:6060/pkg/github.com/spf13/pflag
[3]: http://godoc.org/github.com/spf13/pflag

View File

@ -1,215 +0,0 @@
package pflag
import (
"fmt"
"strconv"
"strings"
"testing"
)
func setUpBSFlagSet(bsp *[]bool) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.BoolSliceVar(bsp, "bs", []bool{}, "Command separated list!")
return f
}
func setUpBSFlagSetWithDefault(bsp *[]bool) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.BoolSliceVar(bsp, "bs", []bool{false, true}, "Command separated list!")
return f
}
func TestEmptyBS(t *testing.T) {
var bs []bool
f := setUpBSFlagSet(&bs)
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
getBS, err := f.GetBoolSlice("bs")
if err != nil {
t.Fatal("got an error from GetBoolSlice():", err)
}
if len(getBS) != 0 {
t.Fatalf("got bs %v with len=%d but expected length=0", getBS, len(getBS))
}
}
func TestBS(t *testing.T) {
var bs []bool
f := setUpBSFlagSet(&bs)
vals := []string{"1", "F", "TRUE", "0"}
arg := fmt.Sprintf("--bs=%s", strings.Join(vals, ","))
err := f.Parse([]string{arg})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range bs {
b, err := strconv.ParseBool(vals[i])
if err != nil {
t.Fatalf("got error: %v", err)
}
if b != v {
t.Fatalf("expected is[%d] to be %s but got: %t", i, vals[i], v)
}
}
getBS, err := f.GetBoolSlice("bs")
if err != nil {
t.Fatalf("got error: %v", err)
}
for i, v := range getBS {
b, err := strconv.ParseBool(vals[i])
if err != nil {
t.Fatalf("got error: %v", err)
}
if b != v {
t.Fatalf("expected bs[%d] to be %s but got: %t from GetBoolSlice", i, vals[i], v)
}
}
}
func TestBSDefault(t *testing.T) {
var bs []bool
f := setUpBSFlagSetWithDefault(&bs)
vals := []string{"false", "T"}
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range bs {
b, err := strconv.ParseBool(vals[i])
if err != nil {
t.Fatalf("got error: %v", err)
}
if b != v {
t.Fatalf("expected bs[%d] to be %t from GetBoolSlice but got: %t", i, b, v)
}
}
getBS, err := f.GetBoolSlice("bs")
if err != nil {
t.Fatal("got an error from GetBoolSlice():", err)
}
for i, v := range getBS {
b, err := strconv.ParseBool(vals[i])
if err != nil {
t.Fatal("got an error from GetBoolSlice():", err)
}
if b != v {
t.Fatalf("expected bs[%d] to be %t from GetBoolSlice but got: %t", i, b, v)
}
}
}
func TestBSWithDefault(t *testing.T) {
var bs []bool
f := setUpBSFlagSetWithDefault(&bs)
vals := []string{"FALSE", "1"}
arg := fmt.Sprintf("--bs=%s", strings.Join(vals, ","))
err := f.Parse([]string{arg})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range bs {
b, err := strconv.ParseBool(vals[i])
if err != nil {
t.Fatalf("got error: %v", err)
}
if b != v {
t.Fatalf("expected bs[%d] to be %t but got: %t", i, b, v)
}
}
getBS, err := f.GetBoolSlice("bs")
if err != nil {
t.Fatal("got an error from GetBoolSlice():", err)
}
for i, v := range getBS {
b, err := strconv.ParseBool(vals[i])
if err != nil {
t.Fatalf("got error: %v", err)
}
if b != v {
t.Fatalf("expected bs[%d] to be %t from GetBoolSlice but got: %t", i, b, v)
}
}
}
func TestBSCalledTwice(t *testing.T) {
var bs []bool
f := setUpBSFlagSet(&bs)
in := []string{"T,F", "T"}
expected := []bool{true, false, true}
argfmt := "--bs=%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 bs {
if expected[i] != v {
t.Fatalf("expected bs[%d] to be %t but got %t", i, expected[i], v)
}
}
}
func TestBSBadQuoting(t *testing.T) {
tests := []struct {
Want []bool
FlagArg []string
}{
{
Want: []bool{true, false, true},
FlagArg: []string{"1", "0", "true"},
},
{
Want: []bool{true, false},
FlagArg: []string{"True", "F"},
},
{
Want: []bool{true, false},
FlagArg: []string{"T", "0"},
},
{
Want: []bool{true, false},
FlagArg: []string{"1", "0"},
},
{
Want: []bool{true, false, false},
FlagArg: []string{"true,false", "false"},
},
{
Want: []bool{true, false, false, true, false, true, false},
FlagArg: []string{`"true,false,false,1,0, T"`, " false "},
},
{
Want: []bool{false, false, true, false, true, false, true},
FlagArg: []string{`"0, False, T,false , true,F"`, "true"},
},
}
for i, test := range tests {
var bs []bool
f := setUpBSFlagSet(&bs)
if err := f.Parse([]string{fmt.Sprintf("--bs=%s", strings.Join(test.FlagArg, ","))}); err != nil {
t.Fatalf("flag parsing failed with error: %s\nparsing:\t%#v\nwant:\t\t%#v",
err, test.FlagArg, test.Want[i])
}
for j, b := range bs {
if b != test.Want[j] {
t.Fatalf("bad value parsed for test %d on bool %d:\nwant:\t%t\ngot:\t%t", i, j, test.Want[j], b)
}
}
}
}

View File

@ -1,179 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pflag
import (
"bytes"
"strconv"
"testing"
)
// This value can be a boolean ("true", "false") or "maybe"
type triStateValue int
const (
triStateFalse triStateValue = 0
triStateTrue triStateValue = 1
triStateMaybe triStateValue = 2
)
const strTriStateMaybe = "maybe"
func (v *triStateValue) IsBoolFlag() bool {
return true
}
func (v *triStateValue) Get() interface{} {
return triStateValue(*v)
}
func (v *triStateValue) Set(s string) error {
if s == strTriStateMaybe {
*v = triStateMaybe
return nil
}
boolVal, err := strconv.ParseBool(s)
if boolVal {
*v = triStateTrue
} else {
*v = triStateFalse
}
return err
}
func (v *triStateValue) String() string {
if *v == triStateMaybe {
return strTriStateMaybe
}
return strconv.FormatBool(*v == triStateTrue)
}
// The type of the flag as required by the pflag.Value interface
func (v *triStateValue) Type() string {
return "version"
}
func setUpFlagSet(tristate *triStateValue) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
*tristate = triStateFalse
flag := f.VarPF(tristate, "tristate", "t", "tristate value (true, maybe or false)")
flag.NoOptDefVal = "true"
return f
}
func TestExplicitTrue(t *testing.T) {
var tristate triStateValue
f := setUpFlagSet(&tristate)
err := f.Parse([]string{"--tristate=true"})
if err != nil {
t.Fatal("expected no error; got", err)
}
if tristate != triStateTrue {
t.Fatal("expected", triStateTrue, "(triStateTrue) but got", tristate, "instead")
}
}
func TestImplicitTrue(t *testing.T) {
var tristate triStateValue
f := setUpFlagSet(&tristate)
err := f.Parse([]string{"--tristate"})
if err != nil {
t.Fatal("expected no error; got", err)
}
if tristate != triStateTrue {
t.Fatal("expected", triStateTrue, "(triStateTrue) but got", tristate, "instead")
}
}
func TestShortFlag(t *testing.T) {
var tristate triStateValue
f := setUpFlagSet(&tristate)
err := f.Parse([]string{"-t"})
if err != nil {
t.Fatal("expected no error; got", err)
}
if tristate != triStateTrue {
t.Fatal("expected", triStateTrue, "(triStateTrue) but got", tristate, "instead")
}
}
func TestShortFlagExtraArgument(t *testing.T) {
var tristate triStateValue
f := setUpFlagSet(&tristate)
// The"maybe"turns into an arg, since short boolean options will only do true/false
err := f.Parse([]string{"-t", "maybe"})
if err != nil {
t.Fatal("expected no error; got", err)
}
if tristate != triStateTrue {
t.Fatal("expected", triStateTrue, "(triStateTrue) but got", tristate, "instead")
}
args := f.Args()
if len(args) != 1 || args[0] != "maybe" {
t.Fatal("expected an extra 'maybe' argument to stick around")
}
}
func TestExplicitMaybe(t *testing.T) {
var tristate triStateValue
f := setUpFlagSet(&tristate)
err := f.Parse([]string{"--tristate=maybe"})
if err != nil {
t.Fatal("expected no error; got", err)
}
if tristate != triStateMaybe {
t.Fatal("expected", triStateMaybe, "(triStateMaybe) but got", tristate, "instead")
}
}
func TestExplicitFalse(t *testing.T) {
var tristate triStateValue
f := setUpFlagSet(&tristate)
err := f.Parse([]string{"--tristate=false"})
if err != nil {
t.Fatal("expected no error; got", err)
}
if tristate != triStateFalse {
t.Fatal("expected", triStateFalse, "(triStateFalse) but got", tristate, "instead")
}
}
func TestImplicitFalse(t *testing.T) {
var tristate triStateValue
f := setUpFlagSet(&tristate)
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
if tristate != triStateFalse {
t.Fatal("expected", triStateFalse, "(triStateFalse) but got", tristate, "instead")
}
}
func TestInvalidValue(t *testing.T) {
var tristate triStateValue
f := setUpFlagSet(&tristate)
var buf bytes.Buffer
f.SetOutput(&buf)
err := f.Parse([]string{"--tristate=invalid"})
if err == nil {
t.Fatal("expected an error but did not get any, tristate has value", tristate)
}
}
func TestBoolP(t *testing.T) {
b := BoolP("bool", "b", false, "bool value in CommandLine")
c := BoolP("c", "c", false, "other bool value")
args := []string{"--bool"}
if err := CommandLine.Parse(args); err != nil {
t.Error("expected no error, got ", err)
}
if *b != true {
t.Errorf("expected b=true got b=%v", *b)
}
if *c != false {
t.Errorf("expect c=false got c=%v", *c)
}
}

View File

@ -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)
}

View 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

View File

@ -1,52 +0,0 @@
package pflag
import (
"os"
"testing"
)
func setUpCount(c *int) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.CountVarP(c, "verbose", "v", "a counter")
return f
}
func TestCount(t *testing.T) {
testCases := []struct {
input []string
success bool
expected int
}{
{[]string{"-vvv"}, true, 3},
{[]string{"-v", "-v", "-v"}, true, 3},
{[]string{"-v", "--verbose", "-v"}, true, 3},
{[]string{"-v=3", "-v"}, true, 4},
{[]string{"-v=a"}, false, 0},
}
devnull, _ := os.Open(os.DevNull)
os.Stderr = devnull
for i := range testCases {
var count int
f := setUpCount(&count)
tc := &testCases[i]
err := f.Parse(tc.input)
if err != nil && tc.success == true {
t.Errorf("expected success, got %q", err)
continue
} else if err == nil && tc.success == false {
t.Errorf("expected failure, got success")
continue
} else if tc.success {
c, err := f.GetCount("verbose")
if err != nil {
t.Errorf("Got error trying to fetch the counter flag")
}
if c != tc.expected {
t.Errorf("expected %q, got %q", tc.expected, c)
}
}
}
}

View File

@ -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)
}

View File

@ -1,36 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pflag_test
import (
"fmt"
"github.com/spf13/pflag"
)
func ExampleShorthandLookup() {
name := "verbose"
short := name[:1]
pflag.BoolP(name, short, false, "verbose output")
// len(short) must be == 1
flag := pflag.ShorthandLookup(short)
fmt.Println(flag.Name)
}
func ExampleFlagSet_ShorthandLookup() {
name := "verbose"
short := name[:1]
fs := pflag.NewFlagSet("Example", pflag.ContinueOnError)
fs.BoolP(name, short, false, "verbose output")
// len(short) must be == 1
flag := fs.ShorthandLookup(short)
fmt.Println(flag.Name)
}

View File

@ -1,29 +0,0 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pflag
import (
"io/ioutil"
"os"
)
// Additional routines compiled into the package only during testing.
// ResetForTesting clears all flag state and sets the usage function as directed.
// After calling ResetForTesting, parse errors in flag handling will not
// exit the program.
func ResetForTesting(usage func()) {
CommandLine = &FlagSet{
name: os.Args[0],
errorHandling: ContinueOnError,
output: ioutil.Discard,
}
Usage = usage
}
// GetCommandLine returns the default FlagSet.
func GetCommandLine() *FlagSet {
return CommandLine
}

View 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,6 +459,7 @@ func (f *FlagSet) Set(name, value string) error {
return fmt.Errorf("invalid argument %q for %q flag: %v", value, flagName, err)
}
if !flag.Changed {
if f.actual == nil {
f.actual = make(map[NormalizedName]*Flag)
}
@ -447,6 +467,7 @@ func (f *FlagSet) Set(name, value string) error {
f.orderedActual = append(f.orderedActual, flag)
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...)
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,14 +948,25 @@ 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
}
}
var value string
if len(split) == 2 {
@ -912,28 +986,44 @@ 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
}
}
var value string
if len(shorthands) > 2 && shorthands[1] == '=' {
@ -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)

File diff suppressed because it is too large Load Diff

View 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)
}

View File

@ -1,39 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pflag
import (
goflag "flag"
"testing"
)
func TestGoflags(t *testing.T) {
goflag.String("stringFlag", "stringFlag", "stringFlag")
goflag.Bool("boolFlag", false, "boolFlag")
f := NewFlagSet("test", ContinueOnError)
f.AddGoFlagSet(goflag.CommandLine)
err := f.Parse([]string{"--stringFlag=bob", "--boolFlag"})
if err != nil {
t.Fatal("expected no error; get", err)
}
getString, err := f.GetString("stringFlag")
if err != nil {
t.Fatal("expected no error; get", err)
}
if getString != "bob" {
t.Fatalf("expected getString=bob but got getString=%s", getString)
}
getBool, err := f.GetBool("boolFlag")
if err != nil {
t.Fatal("expected no error; get", err)
}
if getBool != true {
t.Fatalf("expected getBool=true but got getBool=%v", getBool)
}
}

View File

@ -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)
}

View File

@ -1,165 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pflag
import (
"fmt"
"strconv"
"strings"
"testing"
)
func setUpISFlagSet(isp *[]int) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.IntSliceVar(isp, "is", []int{}, "Command separated list!")
return f
}
func setUpISFlagSetWithDefault(isp *[]int) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.IntSliceVar(isp, "is", []int{0, 1}, "Command separated list!")
return f
}
func TestEmptyIS(t *testing.T) {
var is []int
f := setUpISFlagSet(&is)
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
getIS, err := f.GetIntSlice("is")
if err != nil {
t.Fatal("got an error from GetIntSlice():", err)
}
if len(getIS) != 0 {
t.Fatalf("got is %v with len=%d but expected length=0", getIS, len(getIS))
}
}
func TestIS(t *testing.T) {
var is []int
f := setUpISFlagSet(&is)
vals := []string{"1", "2", "4", "3"}
arg := fmt.Sprintf("--is=%s", strings.Join(vals, ","))
err := f.Parse([]string{arg})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range is {
d, err := strconv.Atoi(vals[i])
if err != nil {
t.Fatalf("got error: %v", err)
}
if d != v {
t.Fatalf("expected is[%d] to be %s but got: %d", i, vals[i], v)
}
}
getIS, err := f.GetIntSlice("is")
if err != nil {
t.Fatalf("got error: %v", err)
}
for i, v := range getIS {
d, err := strconv.Atoi(vals[i])
if err != nil {
t.Fatalf("got error: %v", err)
}
if d != v {
t.Fatalf("expected is[%d] to be %s but got: %d from GetIntSlice", i, vals[i], v)
}
}
}
func TestISDefault(t *testing.T) {
var is []int
f := setUpISFlagSetWithDefault(&is)
vals := []string{"0", "1"}
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range is {
d, err := strconv.Atoi(vals[i])
if err != nil {
t.Fatalf("got error: %v", err)
}
if d != v {
t.Fatalf("expected is[%d] to be %d but got: %d", i, d, v)
}
}
getIS, err := f.GetIntSlice("is")
if err != nil {
t.Fatal("got an error from GetIntSlice():", err)
}
for i, v := range getIS {
d, err := strconv.Atoi(vals[i])
if err != nil {
t.Fatal("got an error from GetIntSlice():", err)
}
if d != v {
t.Fatalf("expected is[%d] to be %d from GetIntSlice but got: %d", i, d, v)
}
}
}
func TestISWithDefault(t *testing.T) {
var is []int
f := setUpISFlagSetWithDefault(&is)
vals := []string{"1", "2"}
arg := fmt.Sprintf("--is=%s", strings.Join(vals, ","))
err := f.Parse([]string{arg})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range is {
d, err := strconv.Atoi(vals[i])
if err != nil {
t.Fatalf("got error: %v", err)
}
if d != v {
t.Fatalf("expected is[%d] to be %d but got: %d", i, d, v)
}
}
getIS, err := f.GetIntSlice("is")
if err != nil {
t.Fatal("got an error from GetIntSlice():", err)
}
for i, v := range getIS {
d, err := strconv.Atoi(vals[i])
if err != nil {
t.Fatalf("got error: %v", err)
}
if d != v {
t.Fatalf("expected is[%d] to be %d from GetIntSlice but got: %d", i, d, v)
}
}
}
func TestISCalledTwice(t *testing.T) {
var is []int
f := setUpISFlagSet(&is)
in := []string{"1,2", "3"}
expected := []int{1, 2, 3}
argfmt := "--is=%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 is {
if expected[i] != v {
t.Fatalf("expected is[%d] to be %d but got: %d", i, expected[i], v)
}
}
}

View File

@ -1,222 +0,0 @@
package pflag
import (
"fmt"
"net"
"strings"
"testing"
)
func setUpIPSFlagSet(ipsp *[]net.IP) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.IPSliceVar(ipsp, "ips", []net.IP{}, "Command separated list!")
return f
}
func setUpIPSFlagSetWithDefault(ipsp *[]net.IP) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.IPSliceVar(ipsp, "ips",
[]net.IP{
net.ParseIP("192.168.1.1"),
net.ParseIP("0:0:0:0:0:0:0:1"),
},
"Command separated list!")
return f
}
func TestEmptyIP(t *testing.T) {
var ips []net.IP
f := setUpIPSFlagSet(&ips)
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
getIPS, err := f.GetIPSlice("ips")
if err != nil {
t.Fatal("got an error from GetIPSlice():", err)
}
if len(getIPS) != 0 {
t.Fatalf("got ips %v with len=%d but expected length=0", getIPS, len(getIPS))
}
}
func TestIPS(t *testing.T) {
var ips []net.IP
f := setUpIPSFlagSet(&ips)
vals := []string{"192.168.1.1", "10.0.0.1", "0:0:0:0:0:0:0:2"}
arg := fmt.Sprintf("--ips=%s", strings.Join(vals, ","))
err := f.Parse([]string{arg})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range ips {
if ip := net.ParseIP(vals[i]); ip == nil {
t.Fatalf("invalid string being converted to IP address: %s", vals[i])
} else if !ip.Equal(v) {
t.Fatalf("expected ips[%d] to be %s but got: %s from GetIPSlice", i, vals[i], v)
}
}
}
func TestIPSDefault(t *testing.T) {
var ips []net.IP
f := setUpIPSFlagSetWithDefault(&ips)
vals := []string{"192.168.1.1", "0:0:0:0:0:0:0:1"}
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range ips {
if ip := net.ParseIP(vals[i]); ip == nil {
t.Fatalf("invalid string being converted to IP address: %s", vals[i])
} else if !ip.Equal(v) {
t.Fatalf("expected ips[%d] to be %s but got: %s", i, vals[i], v)
}
}
getIPS, err := f.GetIPSlice("ips")
if err != nil {
t.Fatal("got an error from GetIPSlice")
}
for i, v := range getIPS {
if ip := net.ParseIP(vals[i]); ip == nil {
t.Fatalf("invalid string being converted to IP address: %s", vals[i])
} else if !ip.Equal(v) {
t.Fatalf("expected ips[%d] to be %s but got: %s", i, vals[i], v)
}
}
}
func TestIPSWithDefault(t *testing.T) {
var ips []net.IP
f := setUpIPSFlagSetWithDefault(&ips)
vals := []string{"192.168.1.1", "0:0:0:0:0:0:0:1"}
arg := fmt.Sprintf("--ips=%s", strings.Join(vals, ","))
err := f.Parse([]string{arg})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range ips {
if ip := net.ParseIP(vals[i]); ip == nil {
t.Fatalf("invalid string being converted to IP address: %s", vals[i])
} else if !ip.Equal(v) {
t.Fatalf("expected ips[%d] to be %s but got: %s", i, vals[i], v)
}
}
getIPS, err := f.GetIPSlice("ips")
if err != nil {
t.Fatal("got an error from GetIPSlice")
}
for i, v := range getIPS {
if ip := net.ParseIP(vals[i]); ip == nil {
t.Fatalf("invalid string being converted to IP address: %s", vals[i])
} else if !ip.Equal(v) {
t.Fatalf("expected ips[%d] to be %s but got: %s", i, vals[i], v)
}
}
}
func TestIPSCalledTwice(t *testing.T) {
var ips []net.IP
f := setUpIPSFlagSet(&ips)
in := []string{"192.168.1.2,0:0:0:0:0:0:0:1", "10.0.0.1"}
expected := []net.IP{net.ParseIP("192.168.1.2"), net.ParseIP("0:0:0:0:0:0:0:1"), net.ParseIP("10.0.0.1")}
argfmt := "ips=%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 ips {
if !expected[i].Equal(v) {
t.Fatalf("expected ips[%d] to be %s but got: %s", i, expected[i], v)
}
}
}
func TestIPSBadQuoting(t *testing.T) {
tests := []struct {
Want []net.IP
FlagArg []string
}{
{
Want: []net.IP{
net.ParseIP("a4ab:61d:f03e:5d7d:fad7:d4c2:a1a5:568"),
net.ParseIP("203.107.49.208"),
net.ParseIP("14.57.204.90"),
},
FlagArg: []string{
"a4ab:61d:f03e:5d7d:fad7:d4c2:a1a5:568",
"203.107.49.208",
"14.57.204.90",
},
},
{
Want: []net.IP{
net.ParseIP("204.228.73.195"),
net.ParseIP("86.141.15.94"),
},
FlagArg: []string{
"204.228.73.195",
"86.141.15.94",
},
},
{
Want: []net.IP{
net.ParseIP("c70c:db36:3001:890f:c6ea:3f9b:7a39:cc3f"),
net.ParseIP("4d17:1d6e:e699:bd7a:88c5:5e7e:ac6a:4472"),
},
FlagArg: []string{
"c70c:db36:3001:890f:c6ea:3f9b:7a39:cc3f",
"4d17:1d6e:e699:bd7a:88c5:5e7e:ac6a:4472",
},
},
{
Want: []net.IP{
net.ParseIP("5170:f971:cfac:7be3:512a:af37:952c:bc33"),
net.ParseIP("93.21.145.140"),
net.ParseIP("2cac:61d3:c5ff:6caf:73e0:1b1a:c336:c1ca"),
},
FlagArg: []string{
" 5170:f971:cfac:7be3:512a:af37:952c:bc33 , 93.21.145.140 ",
"2cac:61d3:c5ff:6caf:73e0:1b1a:c336:c1ca",
},
},
{
Want: []net.IP{
net.ParseIP("2e5e:66b2:6441:848:5b74:76ea:574c:3a7b"),
net.ParseIP("2e5e:66b2:6441:848:5b74:76ea:574c:3a7b"),
net.ParseIP("2e5e:66b2:6441:848:5b74:76ea:574c:3a7b"),
net.ParseIP("2e5e:66b2:6441:848:5b74:76ea:574c:3a7b"),
},
FlagArg: []string{
`"2e5e:66b2:6441:848:5b74:76ea:574c:3a7b, 2e5e:66b2:6441:848:5b74:76ea:574c:3a7b,2e5e:66b2:6441:848:5b74:76ea:574c:3a7b "`,
" 2e5e:66b2:6441:848:5b74:76ea:574c:3a7b"},
},
}
for i, test := range tests {
var ips []net.IP
f := setUpIPSFlagSet(&ips)
if err := f.Parse([]string{fmt.Sprintf("--ips=%s", strings.Join(test.FlagArg, ","))}); err != nil {
t.Fatalf("flag parsing failed with error: %s\nparsing:\t%#v\nwant:\t\t%s",
err, test.FlagArg, test.Want[i])
}
for j, b := range ips {
if !b.Equal(test.Want[j]) {
t.Fatalf("bad value parsed for test %d on net.IP %d:\nwant:\t%s\ngot:\t%s", i, j, test.Want[j], b)
}
}
}
}

View File

@ -1,63 +0,0 @@
package pflag
import (
"fmt"
"net"
"os"
"testing"
)
func setUpIP(ip *net.IP) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.IPVar(ip, "address", net.ParseIP("0.0.0.0"), "IP Address")
return f
}
func TestIP(t *testing.T) {
testCases := []struct {
input string
success bool
expected string
}{
{"0.0.0.0", true, "0.0.0.0"},
{" 0.0.0.0 ", true, "0.0.0.0"},
{"1.2.3.4", true, "1.2.3.4"},
{"127.0.0.1", true, "127.0.0.1"},
{"255.255.255.255", true, "255.255.255.255"},
{"", false, ""},
{"0", false, ""},
{"localhost", false, ""},
{"0.0.0", false, ""},
{"0.0.0.", false, ""},
{"0.0.0.0.", false, ""},
{"0.0.0.256", false, ""},
{"0 . 0 . 0 . 0", false, ""},
}
devnull, _ := os.Open(os.DevNull)
os.Stderr = devnull
for i := range testCases {
var addr net.IP
f := setUpIP(&addr)
tc := &testCases[i]
arg := fmt.Sprintf("--address=%s", tc.input)
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 {
t.Errorf("expected failure")
continue
} else if tc.success {
ip, err := f.GetIP("address")
if err != nil {
t.Errorf("Got error trying to fetch the IP flag: %v", err)
}
if ip.String() != tc.expected {
t.Errorf("expected %q, got %q", tc.expected, ip.String())
}
}
}
}

View File

@ -1,70 +0,0 @@
package pflag
import (
"fmt"
"net"
"os"
"testing"
)
func setUpIPNet(ip *net.IPNet) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
_, def, _ := net.ParseCIDR("0.0.0.0/0")
f.IPNetVar(ip, "address", *def, "IP Address")
return f
}
func TestIPNet(t *testing.T) {
testCases := []struct {
input string
success bool
expected string
}{
{"0.0.0.0/0", true, "0.0.0.0/0"},
{" 0.0.0.0/0 ", true, "0.0.0.0/0"},
{"1.2.3.4/8", true, "1.0.0.0/8"},
{"127.0.0.1/16", true, "127.0.0.0/16"},
{"255.255.255.255/19", true, "255.255.224.0/19"},
{"255.255.255.255/32", true, "255.255.255.255/32"},
{"", false, ""},
{"/0", false, ""},
{"0", false, ""},
{"0/0", false, ""},
{"localhost/0", false, ""},
{"0.0.0/4", false, ""},
{"0.0.0./8", false, ""},
{"0.0.0.0./12", false, ""},
{"0.0.0.256/16", false, ""},
{"0.0.0.0 /20", false, ""},
{"0.0.0.0/ 24", false, ""},
{"0 . 0 . 0 . 0 / 28", false, ""},
{"0.0.0.0/33", false, ""},
}
devnull, _ := os.Open(os.DevNull)
os.Stderr = devnull
for i := range testCases {
var addr net.IPNet
f := setUpIPNet(&addr)
tc := &testCases[i]
arg := fmt.Sprintf("--address=%s", tc.input)
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 {
t.Errorf("expected failure")
continue
} else if tc.success {
ip, err := f.GetIPNet("address")
if err != nil {
t.Errorf("Got error trying to fetch the IP flag: %v", err)
}
if ip.String() != tc.expected {
t.Errorf("expected %q, got %q", tc.expected, ip.String())
}
}
}
}

View 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)
}

View File

@ -1,233 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pflag
import (
"fmt"
"testing"
)
func setUpSAFlagSet(sap *[]string) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.StringArrayVar(sap, "sa", []string{}, "Command separated list!")
return f
}
func setUpSAFlagSetWithDefault(sap *[]string) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.StringArrayVar(sap, "sa", []string{"default", "values"}, "Command separated list!")
return f
}
func TestEmptySA(t *testing.T) {
var sa []string
f := setUpSAFlagSet(&sa)
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
getSA, err := f.GetStringArray("sa")
if err != nil {
t.Fatal("got an error from GetStringArray():", err)
}
if len(getSA) != 0 {
t.Fatalf("got sa %v with len=%d but expected length=0", getSA, len(getSA))
}
}
func TestEmptySAValue(t *testing.T) {
var sa []string
f := setUpSAFlagSet(&sa)
err := f.Parse([]string{"--sa="})
if err != nil {
t.Fatal("expected no error; got", err)
}
getSA, err := f.GetStringArray("sa")
if err != nil {
t.Fatal("got an error from GetStringArray():", err)
}
if len(getSA) != 0 {
t.Fatalf("got sa %v with len=%d but expected length=0", getSA, len(getSA))
}
}
func TestSADefault(t *testing.T) {
var sa []string
f := setUpSAFlagSetWithDefault(&sa)
vals := []string{"default", "values"}
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range sa {
if vals[i] != v {
t.Fatalf("expected sa[%d] to be %s but got: %s", i, vals[i], v)
}
}
getSA, err := f.GetStringArray("sa")
if err != nil {
t.Fatal("got an error from GetStringArray():", err)
}
for i, v := range getSA {
if vals[i] != v {
t.Fatalf("expected sa[%d] to be %s from GetStringArray but got: %s", i, vals[i], v)
}
}
}
func TestSAWithDefault(t *testing.T) {
var sa []string
f := setUpSAFlagSetWithDefault(&sa)
val := "one"
arg := fmt.Sprintf("--sa=%s", val)
err := f.Parse([]string{arg})
if err != nil {
t.Fatal("expected no error; got", err)
}
if len(sa) != 1 {
t.Fatalf("expected number of values to be %d but %d", 1, len(sa))
}
if sa[0] != val {
t.Fatalf("expected value to be %s but got: %s", sa[0], val)
}
getSA, err := f.GetStringArray("sa")
if err != nil {
t.Fatal("got an error from GetStringArray():", err)
}
if len(getSA) != 1 {
t.Fatalf("expected number of values to be %d but %d", 1, len(getSA))
}
if getSA[0] != val {
t.Fatalf("expected value to be %s but got: %s", getSA[0], val)
}
}
func TestSACalledTwice(t *testing.T) {
var sa []string
f := setUpSAFlagSet(&sa)
in := []string{"one", "two"}
expected := []string{"one", "two"}
argfmt := "--sa=%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)
}
if len(expected) != len(sa) {
t.Fatalf("expected number of sa to be %d but got: %d", len(expected), len(sa))
}
for i, v := range sa {
if expected[i] != v {
t.Fatalf("expected sa[%d] to be %s but got: %s", i, expected[i], v)
}
}
values, err := f.GetStringArray("sa")
if err != nil {
t.Fatal("expected no error; got", err)
}
if len(expected) != len(values) {
t.Fatalf("expected number of values to be %d but got: %d", len(expected), len(sa))
}
for i, v := range values {
if expected[i] != v {
t.Fatalf("expected got sa[%d] to be %s but got: %s", i, expected[i], v)
}
}
}
func TestSAWithSpecialChar(t *testing.T) {
var sa []string
f := setUpSAFlagSet(&sa)
in := []string{"one,two", `"three"`, `"four,five",six`, "seven eight"}
expected := []string{"one,two", `"three"`, `"four,five",six`, "seven eight"}
argfmt := "--sa=%s"
arg1 := fmt.Sprintf(argfmt, in[0])
arg2 := fmt.Sprintf(argfmt, in[1])
arg3 := fmt.Sprintf(argfmt, in[2])
arg4 := fmt.Sprintf(argfmt, in[3])
err := f.Parse([]string{arg1, arg2, arg3, arg4})
if err != nil {
t.Fatal("expected no error; got", err)
}
if len(expected) != len(sa) {
t.Fatalf("expected number of sa to be %d but got: %d", len(expected), len(sa))
}
for i, v := range sa {
if expected[i] != v {
t.Fatalf("expected sa[%d] to be %s but got: %s", i, expected[i], v)
}
}
values, err := f.GetStringArray("sa")
if err != nil {
t.Fatal("expected no error; got", err)
}
if len(expected) != len(values) {
t.Fatalf("expected number of values to be %d but got: %d", len(expected), len(values))
}
for i, v := range values {
if expected[i] != v {
t.Fatalf("expected got sa[%d] to be %s but got: %s", i, expected[i], v)
}
}
}
func TestSAWithSquareBrackets(t *testing.T) {
var sa []string
f := setUpSAFlagSet(&sa)
in := []string{"][]-[", "[a-z]", "[a-z]+"}
expected := []string{"][]-[", "[a-z]", "[a-z]+"}
argfmt := "--sa=%s"
arg1 := fmt.Sprintf(argfmt, in[0])
arg2 := fmt.Sprintf(argfmt, in[1])
arg3 := fmt.Sprintf(argfmt, in[2])
err := f.Parse([]string{arg1, arg2, arg3})
if err != nil {
t.Fatal("expected no error; got", err)
}
if len(expected) != len(sa) {
t.Fatalf("expected number of sa to be %d but got: %d", len(expected), len(sa))
}
for i, v := range sa {
if expected[i] != v {
t.Fatalf("expected sa[%d] to be %s but got: %s", i, expected[i], v)
}
}
values, err := f.GetStringArray("sa")
if err != nil {
t.Fatal("expected no error; got", err)
}
if len(expected) != len(values) {
t.Fatalf("expected number of values to be %d but got: %d", len(expected), len(values))
}
for i, v := range values {
if expected[i] != v {
t.Fatalf("expected got sa[%d] to be %s but got: %s", i, expected[i], v)
}
}
}

View 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)
}

View File

@ -1,253 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pflag
import (
"fmt"
"strings"
"testing"
)
func setUpSSFlagSet(ssp *[]string) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.StringSliceVar(ssp, "ss", []string{}, "Command separated list!")
return f
}
func setUpSSFlagSetWithDefault(ssp *[]string) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.StringSliceVar(ssp, "ss", []string{"default", "values"}, "Command separated list!")
return f
}
func TestEmptySS(t *testing.T) {
var ss []string
f := setUpSSFlagSet(&ss)
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
getSS, err := f.GetStringSlice("ss")
if err != nil {
t.Fatal("got an error from GetStringSlice():", err)
}
if len(getSS) != 0 {
t.Fatalf("got ss %v with len=%d but expected length=0", getSS, len(getSS))
}
}
func TestEmptySSValue(t *testing.T) {
var ss []string
f := setUpSSFlagSet(&ss)
err := f.Parse([]string{"--ss="})
if err != nil {
t.Fatal("expected no error; got", err)
}
getSS, err := f.GetStringSlice("ss")
if err != nil {
t.Fatal("got an error from GetStringSlice():", err)
}
if len(getSS) != 0 {
t.Fatalf("got ss %v with len=%d but expected length=0", getSS, len(getSS))
}
}
func TestSS(t *testing.T) {
var ss []string
f := setUpSSFlagSet(&ss)
vals := []string{"one", "two", "4", "3"}
arg := fmt.Sprintf("--ss=%s", strings.Join(vals, ","))
err := f.Parse([]string{arg})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range ss {
if vals[i] != v {
t.Fatalf("expected ss[%d] to be %s but got: %s", i, vals[i], v)
}
}
getSS, err := f.GetStringSlice("ss")
if err != nil {
t.Fatal("got an error from GetStringSlice():", err)
}
for i, v := range getSS {
if vals[i] != v {
t.Fatalf("expected ss[%d] to be %s from GetStringSlice but got: %s", i, vals[i], v)
}
}
}
func TestSSDefault(t *testing.T) {
var ss []string
f := setUpSSFlagSetWithDefault(&ss)
vals := []string{"default", "values"}
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range ss {
if vals[i] != v {
t.Fatalf("expected ss[%d] to be %s but got: %s", i, vals[i], v)
}
}
getSS, err := f.GetStringSlice("ss")
if err != nil {
t.Fatal("got an error from GetStringSlice():", err)
}
for i, v := range getSS {
if vals[i] != v {
t.Fatalf("expected ss[%d] to be %s from GetStringSlice but got: %s", i, vals[i], v)
}
}
}
func TestSSWithDefault(t *testing.T) {
var ss []string
f := setUpSSFlagSetWithDefault(&ss)
vals := []string{"one", "two", "4", "3"}
arg := fmt.Sprintf("--ss=%s", strings.Join(vals, ","))
err := f.Parse([]string{arg})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range ss {
if vals[i] != v {
t.Fatalf("expected ss[%d] to be %s but got: %s", i, vals[i], v)
}
}
getSS, err := f.GetStringSlice("ss")
if err != nil {
t.Fatal("got an error from GetStringSlice():", err)
}
for i, v := range getSS {
if vals[i] != v {
t.Fatalf("expected ss[%d] to be %s from GetStringSlice but got: %s", i, vals[i], v)
}
}
}
func TestSSCalledTwice(t *testing.T) {
var ss []string
f := setUpSSFlagSet(&ss)
in := []string{"one,two", "three"}
expected := []string{"one", "two", "three"}
argfmt := "--ss=%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)
}
if len(expected) != len(ss) {
t.Fatalf("expected number of ss to be %d but got: %d", len(expected), len(ss))
}
for i, v := range ss {
if expected[i] != v {
t.Fatalf("expected ss[%d] to be %s but got: %s", i, expected[i], v)
}
}
values, err := f.GetStringSlice("ss")
if err != nil {
t.Fatal("expected no error; got", err)
}
if len(expected) != len(values) {
t.Fatalf("expected number of values to be %d but got: %d", len(expected), len(ss))
}
for i, v := range values {
if expected[i] != v {
t.Fatalf("expected got ss[%d] to be %s but got: %s", i, expected[i], v)
}
}
}
func TestSSWithComma(t *testing.T) {
var ss []string
f := setUpSSFlagSet(&ss)
in := []string{`"one,two"`, `"three"`, `"four,five",six`}
expected := []string{"one,two", "three", "four,five", "six"}
argfmt := "--ss=%s"
arg1 := fmt.Sprintf(argfmt, in[0])
arg2 := fmt.Sprintf(argfmt, in[1])
arg3 := fmt.Sprintf(argfmt, in[2])
err := f.Parse([]string{arg1, arg2, arg3})
if err != nil {
t.Fatal("expected no error; got", err)
}
if len(expected) != len(ss) {
t.Fatalf("expected number of ss to be %d but got: %d", len(expected), len(ss))
}
for i, v := range ss {
if expected[i] != v {
t.Fatalf("expected ss[%d] to be %s but got: %s", i, expected[i], v)
}
}
values, err := f.GetStringSlice("ss")
if err != nil {
t.Fatal("expected no error; got", err)
}
if len(expected) != len(values) {
t.Fatalf("expected number of values to be %d but got: %d", len(expected), len(values))
}
for i, v := range values {
if expected[i] != v {
t.Fatalf("expected got ss[%d] to be %s but got: %s", i, expected[i], v)
}
}
}
func TestSSWithSquareBrackets(t *testing.T) {
var ss []string
f := setUpSSFlagSet(&ss)
in := []string{`"[a-z]"`, `"[a-z]+"`}
expected := []string{"[a-z]", "[a-z]+"}
argfmt := "--ss=%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)
}
if len(expected) != len(ss) {
t.Fatalf("expected number of ss to be %d but got: %d", len(expected), len(ss))
}
for i, v := range ss {
if expected[i] != v {
t.Fatalf("expected ss[%d] to be %s but got: %s", i, expected[i], v)
}
}
values, err := f.GetStringSlice("ss")
if err != nil {
t.Fatal("expected no error; got", err)
}
if len(expected) != len(values) {
t.Fatalf("expected number of values to be %d but got: %d", len(expected), len(values))
}
for i, v := range values {
if expected[i] != v {
t.Fatalf("expected got ss[%d] to be %s but got: %s", i, expected[i], v)
}
}
}

View File

@ -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)
return &p
}
// 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 StringToInt(name string, value map[string]int, usage string) *map[string]int {
return CommandLine.StringToIntP(name, "", value, usage)
}
// StringToIntP is like StringToInt, but accepts a shorthand letter that can be used after a single dash.
func StringToIntP(name, shorthand string, value map[string]int, usage string) *map[string]int {
return CommandLine.StringToIntP(name, shorthand, value, usage)
}

View File

@ -0,0 +1,160 @@
package pflag
import (
"bytes"
"encoding/csv"
"fmt"
"strings"
)
// -- stringToString Value
type stringToStringValue struct {
value *map[string]string
changed bool
}
func newStringToStringValue(val map[string]string, p *map[string]string) *stringToStringValue {
ssv := new(stringToStringValue)
ssv.value = p
*ssv.value = val
return ssv
}
// Format: a=1,b=2
func (s *stringToStringValue) Set(val string) error {
var ss []string
n := strings.Count(val, "=")
switch n {
case 0:
return fmt.Errorf("%s must be formatted as key=value", val)
case 1:
ss = append(ss, strings.Trim(val, `"`))
default:
r := csv.NewReader(strings.NewReader(val))
var err error
ss, err = r.Read()
if err != nil {
return err
}
}
out := make(map[string]string, 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)
}
out[kv[0]] = kv[1]
}
if !s.changed {
*s.value = out
} else {
for k, v := range out {
(*s.value)[k] = v
}
}
s.changed = true
return nil
}
func (s *stringToStringValue) Type() string {
return "stringToString"
}
func (s *stringToStringValue) String() string {
records := make([]string, 0, len(*s.value)>>1)
for k, v := range *s.value {
records = append(records, k+"="+v)
}
var buf bytes.Buffer
w := csv.NewWriter(&buf)
if err := w.Write(records); err != nil {
panic(err)
}
w.Flush()
return "[" + strings.TrimSpace(buf.String()) + "]"
}
func stringToStringConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// An empty string would cause an empty map
if len(val) == 0 {
return map[string]string{}, nil
}
r := csv.NewReader(strings.NewReader(val))
ss, err := r.Read()
if err != nil {
return nil, err
}
out := make(map[string]string, 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)
}
out[kv[0]] = kv[1]
}
return out, nil
}
// GetStringToString return the map[string]string value of a flag with the given name
func (f *FlagSet) GetStringToString(name string) (map[string]string, error) {
val, err := f.getFlagType(name, "stringToString", stringToStringConv)
if err != nil {
return map[string]string{}, err
}
return val.(map[string]string), nil
}
// StringToStringVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a map[string]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
func (f *FlagSet) StringToStringVar(p *map[string]string, name string, value map[string]string, usage string) {
f.VarP(newStringToStringValue(value, p), name, "", usage)
}
// StringToStringVarP is like StringToStringVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) StringToStringVarP(p *map[string]string, name, shorthand string, value map[string]string, usage string) {
f.VarP(newStringToStringValue(value, p), name, shorthand, usage)
}
// StringToStringVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a map[string]string variable in which to store the value of the flag.
// The value of each argument will not try to be separated by comma
func StringToStringVar(p *map[string]string, name string, value map[string]string, usage string) {
CommandLine.VarP(newStringToStringValue(value, p), name, "", usage)
}
// StringToStringVarP is like StringToStringVar, but accepts a shorthand letter that can be used after a single dash.
func StringToStringVarP(p *map[string]string, name, shorthand string, value map[string]string, usage string) {
CommandLine.VarP(newStringToStringValue(value, p), name, shorthand, usage)
}
// StringToString defines a string flag with specified name, default value, and usage string.
// The return value is the address of a map[string]string variable that stores the value of the flag.
// The value of each argument will not try to be separated by comma
func (f *FlagSet) StringToString(name string, value map[string]string, usage string) *map[string]string {
p := map[string]string{}
f.StringToStringVarP(&p, name, "", value, usage)
return &p
}
// StringToStringP is like StringToString, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) StringToStringP(name, shorthand string, value map[string]string, usage string) *map[string]string {
p := map[string]string{}
f.StringToStringVarP(&p, name, shorthand, value, usage)
return &p
}
// StringToString defines a string flag with specified name, default value, and usage string.
// The return value is the address of a map[string]string variable that stores the value of the flag.
// The value of each argument will not try to be separated by comma
func StringToString(name string, value map[string]string, usage string) *map[string]string {
return CommandLine.StringToStringP(name, "", value, usage)
}
// StringToStringP is like StringToString, but accepts a shorthand letter that can be used after a single dash.
func StringToStringP(name, shorthand string, value map[string]string, usage string) *map[string]string {
return CommandLine.StringToStringP(name, shorthand, value, usage)
}

View File

@ -1,161 +0,0 @@
package pflag
import (
"fmt"
"strconv"
"strings"
"testing"
)
func setUpUISFlagSet(uisp *[]uint) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.UintSliceVar(uisp, "uis", []uint{}, "Command separated list!")
return f
}
func setUpUISFlagSetWithDefault(uisp *[]uint) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.UintSliceVar(uisp, "uis", []uint{0, 1}, "Command separated list!")
return f
}
func TestEmptyUIS(t *testing.T) {
var uis []uint
f := setUpUISFlagSet(&uis)
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
getUIS, err := f.GetUintSlice("uis")
if err != nil {
t.Fatal("got an error from GetUintSlice():", err)
}
if len(getUIS) != 0 {
t.Fatalf("got is %v with len=%d but expected length=0", getUIS, len(getUIS))
}
}
func TestUIS(t *testing.T) {
var uis []uint
f := setUpUISFlagSet(&uis)
vals := []string{"1", "2", "4", "3"}
arg := fmt.Sprintf("--uis=%s", strings.Join(vals, ","))
err := f.Parse([]string{arg})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range uis {
u, err := strconv.ParseUint(vals[i], 10, 0)
if err != nil {
t.Fatalf("got error: %v", err)
}
if uint(u) != v {
t.Fatalf("expected uis[%d] to be %s but got %d", i, vals[i], v)
}
}
getUIS, err := f.GetUintSlice("uis")
if err != nil {
t.Fatalf("got error: %v", err)
}
for i, v := range getUIS {
u, err := strconv.ParseUint(vals[i], 10, 0)
if err != nil {
t.Fatalf("got error: %v", err)
}
if uint(u) != v {
t.Fatalf("expected uis[%d] to be %s but got: %d from GetUintSlice", i, vals[i], v)
}
}
}
func TestUISDefault(t *testing.T) {
var uis []uint
f := setUpUISFlagSetWithDefault(&uis)
vals := []string{"0", "1"}
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range uis {
u, err := strconv.ParseUint(vals[i], 10, 0)
if err != nil {
t.Fatalf("got error: %v", err)
}
if uint(u) != v {
t.Fatalf("expect uis[%d] to be %d but got: %d", i, u, v)
}
}
getUIS, err := f.GetUintSlice("uis")
if err != nil {
t.Fatal("got an error from GetUintSlice():", err)
}
for i, v := range getUIS {
u, err := strconv.ParseUint(vals[i], 10, 0)
if err != nil {
t.Fatal("got an error from GetIntSlice():", err)
}
if uint(u) != v {
t.Fatalf("expected uis[%d] to be %d from GetUintSlice but got: %d", i, u, v)
}
}
}
func TestUISWithDefault(t *testing.T) {
var uis []uint
f := setUpUISFlagSetWithDefault(&uis)
vals := []string{"1", "2"}
arg := fmt.Sprintf("--uis=%s", strings.Join(vals, ","))
err := f.Parse([]string{arg})
if err != nil {
t.Fatal("expected no error; got", err)
}
for i, v := range uis {
u, err := strconv.ParseUint(vals[i], 10, 0)
if err != nil {
t.Fatalf("got error: %v", err)
}
if uint(u) != v {
t.Fatalf("expected uis[%d] to be %d from GetUintSlice but got: %d", i, u, v)
}
}
getUIS, err := f.GetUintSlice("uis")
if err != nil {
t.Fatal("got an error from GetUintSlice():", err)
}
for i, v := range getUIS {
u, err := strconv.ParseUint(vals[i], 10, 0)
if err != nil {
t.Fatalf("got error: %v", err)
}
if uint(u) != v {
t.Fatalf("expected uis[%d] to be %d from GetUintSlice but got: %d", i, u, v)
}
}
}
func TestUISCalledTwice(t *testing.T) {
var uis []uint
f := setUpUISFlagSet(&uis)
in := []string{"1,2", "3"}
expected := []int{1, 2, 3}
argfmt := "--uis=%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 uis {
if uint(expected[i]) != v {
t.Fatalf("expected uis[%d] to be %d but got: %d", i, expected[i], v)
}
}
}

View File

@ -1,10 +0,0 @@
# Treat all files in this repo as binary, with no git magic updating
# line endings. Windows users contributing to Go will need to use a
# modern version of git and editors capable of LF line endings.
#
# We'll prevent accidental CRLF line endings from entering the repo
# via the git-review gofmt checks.
#
# See golang.org/issue/9281
* -text

View File

@ -1,2 +0,0 @@
# Add no patterns to .hgignore except for files generated by the build.
last-change

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