diff --git a/.ayi.yml b/.ayi.yml index d63ad76..6d5b036 100644 --- a/.ayi.yml +++ b/.ayi.yml @@ -1,10 +1,10 @@ dep-install: - dep ensure test: - - go test -v -cover ./cast/... ./config/... ./generator/... ./log/... ./requests/... ./structure/... ./util/... + - go test -v -cover ./cast/... ./config/... ./errors/... ./generator/... ./log/... ./noodle/... ./requests/... ./structure/... ./util/... scripts: fmt: - - gofmt -d -l -w ./cast ./config ./log ./requests ./structure ./util + - gofmt -d -l -w ./cast ./config ./errors ./log ./requests ./structure ./util - git status check: # TODO: didn't see any error so far ... diff --git a/.travis.yml b/.travis.yml index fa16d3f..84f78a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,5 +13,7 @@ install: script: - make install - - go test -v -cover ./cast/... ./config/... ./generator/... ./log/... ./noodle/... ./requests/... ./runner/... ./structure/... ./util/... + - make test + - make test-race + - make vet # TODO: gommon doesn't have coverage ... amazing .. \ No newline at end of file diff --git a/Gopkg.lock b/Gopkg.lock index 87e67a3..4a82fec 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -13,12 +13,6 @@ packages = ["."] revision = "cd60e84ee657ff3dc51de0b4f55dd299a3e136f2" -[[projects]] - name = "github.com/pkg/errors" - packages = ["."] - revision = "645ef00459ed84a119197bfb8d8205042c6df63d" - version = "v0.8.0" - [[projects]] name = "github.com/pmezard/go-difflib" packages = ["difflib"] @@ -35,17 +29,17 @@ branch = "master" name = "golang.org/x/net" packages = ["proxy"] - revision = "2fb46b16b8dda405028c50f7c7f0f9dd1fa6bfb1" + revision = "cbe0f9307d0156177f9dd5dc85da1a31abc5f2fb" [[projects]] - branch = "v2" name = "gopkg.in/yaml.v2" packages = ["."] - revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4" + revision = "7f97868eec74b32b0982dd158a51a446d1da7eb5" + version = "v2.1.1" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "f3cd24d5f84b06d6778542731631e85ba94ebdad3a0e6b2ee28e5b75021aa98e" + inputs-digest = "12db862c5f0874f36c16cc991c2340defc029e3bf79457980deaf3e06b9e598d" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 935e8ad..19b742c 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -27,13 +27,11 @@ ignored = [ "github.com/dyweb/gommon/legacy", + "github.com/dyweb/gommon/legacy/*", "github.com/dyweb/gommon/playground", + "github.com/dyweb/gommon/playground/*", ] -[[constraint]] - name = "github.com/pkg/errors" - version = "0.8.0" - [prune] go-tests = true unused-packages = true diff --git a/Makefile b/Makefile index ca525da..663d887 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,17 @@ +PKGS=./cast/... ./config/... ./errors/... ./generator/... ./log/... ./noodle/... ./requests/... ./structure/... ./util/... +PKGST=./cast ./config ./errors ./generator ./log ./noodle ./requests ./structure ./util + .PHONY: install install: go install ./cmd/gommon .PHONY: test test: - go test -v -cover ./cast/... ./config/... ./generator/... ./log/... ./noodle/... ./requests/... ./structure/... ./util/... + go test -v -cover $(PKGS) + +.PHONY: test-race +test-race: + go test -race $(PKGS) .PHONY: test-log test-log: @@ -12,7 +19,16 @@ test-log: .PHONY: fmt fmt: - gofmt -d -l -w ./cast ./config ./generator ./log ./noodle ./requests ./structure ./util + gofmt -d -l -w $(PKGST) + +.PHONY: vet +vet: + go vet $(PKGST) + +.PHONy: doc +doc: + xdg-open http://localhost:6060/pkg/github.com/dyweb/gommon & + godoc -http=":6060" .PHONY: docker-test docker-test: diff --git a/README.md b/README.md index 458229b..1b18475 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,14 @@ Gommon is a collection of common util libraries written in Go. It has the following components: -- [Config](config) A YAML config reader with template support -- [Log](log) A Javaish logger for Go, application can control library and set level for different pkg via config or flag -- [Generator](generator) Render go template, generate methods for logger interface based on `gommon.yml` -- [Noodle](noodle) Embed static assets for web application with `.noodleignore` support -- [Requests](requests) A pythonic wrapper for `net/http`, HTTP for Gopher. -- [Cast](cast) Convert Golang types -- [Data structure](structure) Bring Set etc. to Golang. +- [config](config) A YAML config reader with template support +- [errors](errors) Wrap error and multi error +- [generator](generator) Render go template, generate methods for logger interface based on `gommon.yml` +- [log](log) A Javaish logger for Go, application can control library and set level for different pkg via config or flag +- [noodle](noodle) Embed static assets for web application with `.noodleignore` support +- [requests](requests) A pythonic wrapper for `net/http`, HTTP for Gopher. +- [cast](cast) Convert Golang types +- [structure](structure) Bring data structure like Set etc. to Golang. Legacy @@ -26,16 +27,18 @@ Legacy ## Dependencies -Currently we only have two non standard library dependencies, see [Gopkg.lock](Gopkg.lock), -we might replace them with our own implementation in the future. +Currently we only have one non standard library dependencies, see [Gopkg.lock](Gopkg.lock) -- [pkg/errors](https://github.com/pkg/errors) for including context in error - - however it's still not very machine friendly, we are likely to opt it out in future version - [go-yaml/yaml](https://github.com/go-yaml/yaml) for read config written in YAML - we don't need most feature of YAML, and want to have access to the parser directly to report which line has incorrect semantic (after checking it in application). - might write one in [ANTLR](https://github.com/antlr/antlr4) - we also have a DSL work in progress [RCL: Reika Configuration Language](https://github.com/at15/reika/issues/49), which is like [HCL](https://github.com/hashicorp/hcl2) +Removed + +- [pkg/errors](https://github.com/pkg/errors) for including context in error + - removed in [#59](https://github.com/dyweb/gommon/pull/59) + @@ -66,23 +69,46 @@ Currently, gommon is in a very violate state, please open issues after it become Gommon is inspired by the following awesome libraries, most gommon packages have much less features and a few improvements compared to packages it modeled after. +log + - [sirupsen/logrus](https://github.com/sirupsen/logrus) for structured logging - log v1 is entirely modeled after logrus, entry contains log information with methods like `Info`, `Infof` - [apex/log](https://github.com/apex/log) for log handlers - log v2's handler is inspired by apex/log, but we didn't use entry and chose to pass multiple parameters to explicitly state what a handler should handle - [uber-go/zap](https://github.com/uber-go/zap) for serialize log fields without using `fmt.Sprintf` and use `strconv` directly - we didn't go that extreme as Zap or ZeroLog for zero allocation, performance is not our goal for now + +config + - [spf13/cast](https://github.com/spf13/cast) for cast, it is used by Viper - [spf13/viper](https://github.com/spf13/viper/) for config - looking up config via string key makes type system useless, so we always marshal entire config file to a single struct - it also makes refactor easier + +requests + - [Requests](http://docs.python-requests.org/en/master/) for requests +- [hashicorp/go-cleanhttp](https://github.com/hashicorp/go-cleanhttp) for using non default http transport and client + +generator + - [benbjohnson/tmpl](https://github.com/benbjohnson/tmpl) for go template generator - first saw it in [influxdata/influxdb](https://github.com/influxdata/influxdb/blob/master/tsdb/engine/tsm1/encoding.gen.go.tmpl) - we put template data in `gommon.yml`, so we don't need to pass data as json via cli -- [GeertJohan/go.rice](https://github.com/GeertJohan/go.rice) for ~~rice~~ noodle + +noodle + +- [GeertJohan/go.rice](https://github.com/GeertJohan/go.rice) - we implemented `.gitignore` like [feature](https://github.com/at15/go.rice/issues/1) but the upstream didn't respond for the [feature request #83](https://github.com/GeertJohan/go.rice/issues/83) +errors + +- [pkg/errors](https://github.com/pkg/errors) it can not introduce breaking change, but `WithMessage` and `WithStack` is annoying + - see [#54](https://github.com/dyweb/gommon/issues/54) and [errors/doc](errors/doc) about other error packages + - https://github.com/pkg/errors/pull/122 for check existing stack before attach new one +- [uber-go/multierr#21]( https://github.com/uber-go/multierr/issues/21) for return bool after append +- [hashicorp/go-multierror](https://github.com/hashicorp/go-multierror) for `ErrorOrNil` + ## About It was part of [Ayi](https://github.com/dyweb/Ayi) and split out for wider use. diff --git a/cast/cast.go b/cast/cast.go index 9da093b..f1c99e0 100644 --- a/cast/cast.go +++ b/cast/cast.go @@ -2,7 +2,8 @@ package cast import ( "encoding/json" - "github.com/pkg/errors" + + "github.com/dyweb/gommon/errors" "gopkg.in/yaml.v2" ) diff --git a/cast/doc.go b/cast/doc.go deleted file mode 100644 index 9c4f41f..0000000 --- a/cast/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package cast convert types safely and drop incompatible types during conversion -// Ref https://github.com/spf13/cast -package cast diff --git a/cast/pkg.go b/cast/pkg.go index 4e6c8aa..953b788 100644 --- a/cast/pkg.go +++ b/cast/pkg.go @@ -1 +1,4 @@ +// Package cast convert types safely and drop incompatible types during conversion +// +// it is inspired by https://github.com/spf13/cast package cast diff --git a/cmd/gommon/main.go b/cmd/gommon/main.go index 9b3dc15..144d3a1 100644 --- a/cmd/gommon/main.go +++ b/cmd/gommon/main.go @@ -1,3 +1,4 @@ +// gommon is the commandline util for generator package main import ( diff --git a/config/doc.go b/config/doc.go deleted file mode 100644 index 215a0a7..0000000 --- a/config/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -/* -Package config support django syntax in yaml config -*/ -package config diff --git a/config/gommon_generated.go b/config/gommon_generated.go index c266507..62bba24 100644 --- a/config/gommon_generated.go +++ b/config/gommon_generated.go @@ -1,4 +1,5 @@ // Code generated by gommon from config/gommon.yml DO NOT EDIT. + package config import dlog "github.com/dyweb/gommon/log" diff --git a/config/pkg.go b/config/pkg.go index 6a53eee..8c131db 100644 --- a/config/pkg.go +++ b/config/pkg.go @@ -1,3 +1,4 @@ +// Package config supports go text/template, environment and self defined variables package config import ( diff --git a/config/yaml.go b/config/yaml.go index d7c5a8b..228d6ae 100644 --- a/config/yaml.go +++ b/config/yaml.go @@ -2,7 +2,6 @@ package config import ( "bytes" - "fmt" "io/ioutil" "os" "reflect" @@ -10,11 +9,12 @@ import ( "sync" "text/template" + "gopkg.in/yaml.v2" + "github.com/dyweb/gommon/cast" + "github.com/dyweb/gommon/errors" dlog "github.com/dyweb/gommon/log" "github.com/dyweb/gommon/util" - "github.com/pkg/errors" - "gopkg.in/yaml.v2" ) // YAMLConfig is a thread safe struct for parse YAML file and get value @@ -35,10 +35,10 @@ func LoadYAMLAsStruct(file string, structuredConfig interface{}) error { } c := NewYAMLConfig() if err := c.ParseSingleDocument(b); err != nil { - return errors.WithMessage(err, "can't parse as single document") + return errors.Wrap(err, "can't parse as single document") } if err := c.Unmarshal(structuredConfig, true); err != nil { - return errors.WithMessage(err, fmt.Sprintf("can't turn file %s into structured config", file)) + return errors.Wrapf(err, "can't turn file %s into structured config", file) } return nil } diff --git a/errors/README.md b/errors/README.md new file mode 100644 index 0000000..a5d3598 --- /dev/null +++ b/errors/README.md @@ -0,0 +1,5 @@ +# Errors + +A error package supports wrapping and multi error + +- init https://github.com/dyweb/gommon/issues/54 \ No newline at end of file diff --git a/errors/doc/README.md b/errors/doc/README.md new file mode 100644 index 0000000..a9d8817 --- /dev/null +++ b/errors/doc/README.md @@ -0,0 +1,32 @@ +# Doc + +## Survey + +wrap + +- [pkg/errors](pkg-errors.md) +- [juju/errgo](juju-errgo.md) + - support `Mask`, which is not seen in other packages +- [juju/errors](juju-errors.md) + - same as errgo, and not depending on it, the description is [outdated](https://github.com/juju/errors/pull/10) +- [hashicorp/errwrap](hashicorp-errwrap.md) + - contains string or type + - get error by string or type + +multi error + +- [hashicorp/go-multierror](hashicorp-go-multierror.md) + - flatten multierror when append + - ErrorOrNil + - https://golang.org/doc/faq#nil_error Why is my nil error value not equal to nil? +- [uber/multierr](uber-multierr.md) + - combine more than one error + - [x] has a `atomic.Book` for copyNeeded, seems only used for fast path + +TODO: k8s + +- [ ] https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/util/errors/errors.go +- [ ] https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/util/validation/field/errors.go + + +- implement Formatter interface `Format(f State, c rune)` https://golang.org/pkg/fmt/#Formatter \ No newline at end of file diff --git a/errors/doc/hashicorp-errwrap.md b/errors/doc/hashicorp-errwrap.md new file mode 100644 index 0000000..6d11060 --- /dev/null +++ b/errors/doc/hashicorp-errwrap.md @@ -0,0 +1,45 @@ +# hashicorp/errwrap + +- repo: https://github.com/hashicorp/errwrap +- doc: https://godoc.org/github.com/hashicorp/errwrap +- last update: 2014/10/07 + +Usage + +- wrap +- check contains +- get error(s) based on string/type +- walk + +````go +func Contains(err error, msg string) bool +func ContainsType(err error, v interface{}) bool +func GetAll(err error, msg string) []error +func GetAllType(err error, v interface{}) []error +func Walk(err error, cb WalkFunc) +func Wrap(outer, inner error) error +func Wrapf(format string, err error) error +type WalkFunc func(error) +type Wrapper interface { + WrappedErrors() []error +} +```` + +- I was thinking it is using `strings.Contains` but it is actually comparing message directly ... + +````go +// GetAll gets all the errors that might be wrapped in err with the +// given message. The order of the errors is such that the outermost +// matching error (the most recent wrap) is index zero, and so on. +func GetAll(err error, msg string) []error { + var result []error + + Walk(err, func(err error) { + if err.Error() == msg { + result = append(result, err) + } + }) + + return result +} +```` \ No newline at end of file diff --git a/errors/doc/hashicorp-go-multierror.md b/errors/doc/hashicorp-go-multierror.md new file mode 100644 index 0000000..5ba3dd5 --- /dev/null +++ b/errors/doc/hashicorp-go-multierror.md @@ -0,0 +1,37 @@ +# hashicorp/go-multierror + +- repo: https://github.com/hashicorp/go-multierror +- doc: https://godoc.org/github.com/hashicorp/go-multierror +- last update: 2017/12/04 + +Usage + +- append +- custom format function +- **flatten** + +````go +type Error struct { + Errors []error + ErrorFormat ErrorFormatFunc +} + +func (e *Error) ErrorOrNil() error { + if e == nil { + return nil + } + if len(e.Errors) == 0 { + return nil + } + + return e +} + +func (e *Error) GoString() string { + return fmt.Sprintf("*%#v", *e) +} + +// kind of similar to what is wanted in uber-multierr https://github.com/uber-go/multierr/issues/21 +var result *multierror.Error +return result.ErrorOrNil() +```` \ No newline at end of file diff --git a/errors/doc/juju-errgo.md b/errors/doc/juju-errgo.md new file mode 100644 index 0000000..9d47bb8 --- /dev/null +++ b/errors/doc/juju-errgo.md @@ -0,0 +1,19 @@ +# juju/errgo + +- repo: https://github.com/juju/errgo +- doc: https://godoc.org/github.com/juju/errgo +- last update: 2014/09/25 + +Usage + +- [ ] Mask ... https://github.com/juju/errgo#func--mask +- cause + +````go +func Any(error) bool +func Cause(err error) error +// return stack etc. in a single string +func Details(err error) string +func Mask(underlying error, pass ...func(error) bool) error +func WithCausef(underlying, cause error, f string, a ...interface{}) error +```` \ No newline at end of file diff --git a/errors/doc/juju-errors.md b/errors/doc/juju-errors.md new file mode 100644 index 0000000..d0abcff --- /dev/null +++ b/errors/doc/juju-errors.md @@ -0,0 +1,71 @@ +# juju/errors + +- repo: https://github.com/juju/errors +- doc: https://godoc.org/github.com/juju/errors +- last update: 2017/06/02 + +- it is not using errgo ... https://github.com/juju/errors/pull/10 + - the description of repo is misleading ... + + +Usage + +- mostly same as juju-errgo +- location is ~~not set when error is created~~ when using `Wrapf` etc. location is set +- [ ] its way of obtain go path may not work when `vendor` is used ... + +````go +// Err holds a description of an error along with information about +// where the error was created. +// +// It may be embedded in custom error types to add extra information that +// this errors package can understand. +type Err struct { + // message holds an annotation of the error. + message string + + // cause holds the cause of the error as returned + // by the Cause method. + cause error + + // previous holds the previous error in the error stack, if any. + previous error + + // file and line hold the source code location where the error was + // created. + file string + line int +} + + +// Cause returns the cause of the given error. This will be either the +// original error, or the result of a Wrap or Mask call. +// +// Cause is the usual way to diagnose errors that may have been wrapped by +// the other errors functions. +func Cause(err error) error { + var diag error + if err, ok := err.(causer); ok { + diag = err.Cause() + } + if diag != nil { + return diag + } + return err +} + +type causer interface { + Cause() error +} + +type wrapper interface { + // Message returns the top level error message, + // not including the message from the Previous + // error. + Message() string + + // Underlying returns the Previous error, or nil + // if there is none. + Underlying() error +} +```` \ No newline at end of file diff --git a/errors/doc/pkg-errors.md b/errors/doc/pkg-errors.md new file mode 100644 index 0000000..437c6b5 --- /dev/null +++ b/errors/doc/pkg-errors.md @@ -0,0 +1,85 @@ +# pkg/errors + +- repo: https://github.com/pkg/errors +- doc: http://godoc.org/github.com/pkg/errors +- last update: 2018/01/26 + +Pending issues + +- [ ] `"Avoid FuncForPC, use CallersFrames` https://github.com/pkg/errors/issues/107 +- [ ] use same format verb as go-stack/stack https://github.com/pkg/errors/issues/38 +- [ ] Wrap() add withStack only if no cause with stack present https://github.com/pkg/errors/pull/122 + +Usage + +- wrap error +- get cause +- contains stack trace + +```go +// wrap errors from standard library +_, err := ioutil.ReadAll(r) +if err != nil { + return errors.Wrap(err, "read failed") +} + +// get cause +type causer interface { + Cause() error +} +switch err := errors.Cause(err).(type) { +case *MyError: + // handle specifically +default: + // unknown error +} + +type stackTracer interface { + StackTrace() errors.StackTrace +} +type StackTrace []Frame +``` + +Internal + +- stack call depth is hard coded to 32 + +````go +type withMessage struct { + cause error + msg string +} + +type withStack struct { + error + *stack +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} +```` \ No newline at end of file diff --git a/errors/doc/uber-multierr.md b/errors/doc/uber-multierr.md new file mode 100644 index 0000000..0769a67 --- /dev/null +++ b/errors/doc/uber-multierr.md @@ -0,0 +1,65 @@ +# uber-go/multierr + +- repo: https://github.com/uber-go/multierr +- doc: https://godoc.org/go.uber.org/multierr + +Pending + +- [ ] zap integration https://github.com/uber-go/multierr/issues/23 +- [ ] return two value for append https://github.com/uber-go/multierr/issues/21 + +TODO + +- [ ] it seems it can't handle concurrent access? it has an atomic value `copyNeeded` + +Usage + +- Combine and Append + +````go +multierr.Combine( + reader.Close(), + writer.Close(), + conn.Close(), +) +err = multierr.Append(reader.Close(), writer.Close()) +```` + +````go +type multiError struct { + copyNeeded atomic.Bool + errors []error +} + +```` + +- append has fast path, which use `atomic.Bool` to determine is copy is needed +- flatten multierrors + +````go +func Append(left error, right error) error { + switch { + case left == nil: + return right + case right == nil: + return left + } + + if _, ok := right.(*multiError); !ok { + if l, ok := left.(*multiError); ok && !l.copyNeeded.Swap(true) { + // Common case where the error on the left is constantly being + // appended to. + errs := append(l.errors, right) + return &multiError{errors: errs} + } else if !ok { + // Both errors are single errors. + return &multiError{errors: []error{left, right}} + } + } + + // Either right or both, left and right, are multiErrors. Rely on usual + // expensive logic. + errors := [2]error{left, right} + return fromSlice(errors[0:]) +} +```` \ No newline at end of file diff --git a/errors/errors.go b/errors/errors.go new file mode 100644 index 0000000..0156e02 --- /dev/null +++ b/errors/errors.go @@ -0,0 +1,85 @@ +package errors + +import "fmt" + +type TracedError interface { + ErrorStack() *Stack +} + +var _ error = (*FreshError)(nil) +var _ TracedError = (*FreshError)(nil) + +type FreshError struct { + msg string + stack *Stack +} + +func (fresh *FreshError) Error() string { + return fresh.msg +} + +func (fresh *FreshError) ErrorStack() *Stack { + return fresh.stack +} + +var _ error = (*WrappedError)(nil) +var _ TracedError = (*WrappedError)(nil) + +type WrappedError struct { + msg string + cause error + stack *Stack +} + +func Wrap(err error, msg string) error { + // NOTE: sometimes we call wrap without check if the error is nil, it is cleaner if it is the last statement in func + // + // i.e. return errors.Wrap(f.Close(), "failed to close file") + // + // if err := f.Close(); err != nil { + // return errors.Wrap(err, "failed to close file") + // } + // return nil + if err == nil { + return nil + } + var stack *Stack + // reuse existing stack + if t, ok := err.(TracedError); ok { + stack = t.ErrorStack() + } else { + stack = callers() + } + return &WrappedError{ + msg: msg, + cause: err, + stack: stack, + } +} + +func Wrapf(err error, format string, args ...interface{}) error { + // NOTE: copied from wrap instead of call Wrap due to caller + if err == nil { + return nil + } + var stack *Stack + // reuse existing stack + if t, ok := err.(TracedError); ok { + stack = t.ErrorStack() + } else { + stack = callers() + } + return &WrappedError{ + msg: fmt.Sprintf(format, args...), + cause: err, + stack: stack, + } +} + +func (wrapped *WrappedError) Error() string { + return wrapped.msg + ": " + wrapped.cause.Error() +} + +func (wrapped *WrappedError) ErrorStack() *Stack { + return wrapped.stack +} diff --git a/errors/errors_test.go b/errors/errors_test.go new file mode 100644 index 0000000..6bf0ba4 --- /dev/null +++ b/errors/errors_test.go @@ -0,0 +1,51 @@ +package errors + +import ( + "os" + "testing" + + asst "github.com/stretchr/testify/assert" +) + +func TestNew(t *testing.T) { + assert := asst.New(t) + err := New("don't let me go") + assert.NotNil(err) + terr, ok := err.(TracedError) + assert.True(ok) + printFrames(terr.ErrorStack().Frames()) + assert.Equal(3, len(terr.ErrorStack().Frames())) +} + +func freshErr() error { + return New("I am a fresh error") +} + +func wrappedStdErr() error { + return Wrap(os.ErrClosed, "can't open closed file") +} + +func TestWrap(t *testing.T) { + assert := asst.New(t) + + n := Wrap(nil, "nothing") + assert.Nil(n) + + errw := Wrap(os.ErrClosed, "can't open closed file") + terr, ok := errw.(TracedError) + assert.True(ok) + printFrames(terr.ErrorStack().Frames()) + assert.Equal(3, len(terr.ErrorStack().Frames())) + + errw = Wrap(freshErr(), "wrap again") + terr, ok = errw.(TracedError) + assert.True(ok) + printFrames(terr.ErrorStack().Frames()) + assert.Equal(4, len(terr.ErrorStack().Frames())) + + errw = Wrap(wrappedStdErr(), "wrap again") + terr, ok = errw.(TracedError) + assert.True(ok) + printFrames(terr.ErrorStack().Frames()) + assert.Equal(4, len(terr.ErrorStack().Frames())) +} diff --git a/errors/multi.go b/errors/multi.go new file mode 100644 index 0000000..8c1e26c --- /dev/null +++ b/errors/multi.go @@ -0,0 +1,143 @@ +package errors + +import ( + "strconv" + "sync" +) + +type MultiErr interface { + error + // Append returns true if the appended error is not nil, inspired by https://github.com/uber-go/multierr/issues/21 + Append(error) bool + Errors() []error + // Error returns itself or nil if there are no errors, inspired by https://github.com/hashicorp/go-multierror + ErrorOrNil() error + // HasError is ErrorOrNil != nil + HasError() bool +} + +func NewMultiErr() MultiErr { + return &multiErr{} +} + +func NewMultiErrSafe() MultiErr { + return &multiErrSafe{} +} + +var _ MultiErr = (*multiErr)(nil) + +type multiErr struct { + errs []error +} + +func (m *multiErr) Append(err error) bool { + if err == nil { + return false + } + if mErr, ok := err.(MultiErr); ok { + m.errs = append(m.errs, mErr.Errors()...) + } else { + m.errs = append(m.errs, err) + } + return true +} + +func (m *multiErr) Errors() []error { + return m.errs +} + +func (m *multiErr) Error() string { + return formatErrors(m.errs) +} + +func (m *multiErr) ErrorOrNil() error { + if m == nil || len(m.errs) == 0 { + return nil + } + return m +} + +func (m *multiErr) HasError() bool { + if m == nil || len(m.errs) == 0 { + return false + } + return true +} + +var _ MultiErr = (*multiErrSafe)(nil) + +type multiErrSafe struct { + mu sync.Mutex + errs []error +} + +func (m *multiErrSafe) Append(err error) bool { + if err == nil { + return false + } + m.mu.Lock() + if mErr, ok := err.(MultiErr); ok { + m.errs = append(m.errs, mErr.Errors()...) + } else { + m.errs = append(m.errs, err) + } + m.mu.Unlock() + return true +} + +func (m *multiErrSafe) Errors() []error { + m.mu.Lock() + t := make([]error, len(m.errs)) + copy(t, m.errs) + m.mu.Unlock() + return t +} + +func (m *multiErrSafe) Error() string { + m.mu.Lock() + s := formatErrors(m.errs) + m.mu.Unlock() + return s +} + +func (m *multiErrSafe) ErrorOrNil() error { + if m == nil { + return nil + } + m.mu.Lock() + if len(m.errs) == 0 { + m.mu.Unlock() + return nil + } else { + m.mu.Unlock() + return m + } +} + +func (m *multiErrSafe) HasError() bool { + if m == nil { + return false + } + m.mu.Lock() + if len(m.errs) == 0 { + m.mu.Unlock() + return false + } else { + m.mu.Unlock() + return true + } +} + +func formatErrors(errs []error) string { + if len(errs) == 1 { + return errs[0].Error() + } + buf := make([]byte, 0, len(errs)*10) + buf = strconv.AppendInt(buf, int64(len(errs)), 10) + buf = append(buf, " errors; "...) + for i := range errs { + buf = append(buf, errs[i].Error()...) + buf = append(buf, MultiErrSep...) + } + return string(buf[:len(buf)-2]) +} diff --git a/errors/multi_race_test.go b/errors/multi_race_test.go new file mode 100644 index 0000000..94d33c1 --- /dev/null +++ b/errors/multi_race_test.go @@ -0,0 +1,34 @@ +// +build !race + +package errors + +import ( + "os" + "sync" + "testing" + + asst "github.com/stretchr/testify/assert" +) + +// NOTE: when adding build tag, remember to have a blank line between it and package name, otherwise it is treated as comment +func TestMultiErr_Append(t *testing.T) { + assert := asst.New(t) + merr := NewMultiErr() + nRoutine := 10 + errPerRoutine := 20 + var wg sync.WaitGroup + wg.Add(nRoutine) + for i := 0; i < nRoutine; i++ { + go func() { + for j := 0; j < errPerRoutine; j++ { + merr.Append(os.ErrClosed) + } + wg.Done() + }() + } + wg.Wait() + // due to data race, the actual number of errors should be smaller than what we expected + // NOTE: we add = because sometimes there is no race .... + assert.True(nRoutine*errPerRoutine >= len(merr.Errors())) + t.Logf("expect %d actual %d", nRoutine*errPerRoutine, len(merr.Errors())) +} diff --git a/errors/multi_test.go b/errors/multi_test.go new file mode 100644 index 0000000..52746ba --- /dev/null +++ b/errors/multi_test.go @@ -0,0 +1,78 @@ +package errors + +import ( + "os" + "sync" + "testing" + + asst "github.com/stretchr/testify/assert" +) + +func TestMultiErrSafe_Append(t *testing.T) { + assert := asst.New(t) + merr := NewMultiErrSafe() + nRoutine := 10 + errPerRoutine := 20 + var wg sync.WaitGroup + wg.Add(nRoutine) + for i := 0; i < nRoutine; i++ { + go func() { + for j := 0; j < errPerRoutine; j++ { + merr.Append(os.ErrClosed) + } + wg.Done() + }() + } + wg.Wait() + assert.Equal(nRoutine*errPerRoutine, len(merr.Errors())) +} + +func TestMultiErr_AppendReturn(t *testing.T) { + // NOTE: inspired by https://github.com/uber-go/multierr/issues/21 + errs := map[string]func() MultiErr{ + "unsafe": NewMultiErr, + "safe": NewMultiErrSafe, + } + for name := range errs { + t.Run(name, func(t *testing.T) { + assert := asst.New(t) + merr := errs[name]() + assert.False(merr.Append(nil)) + assert.True(merr.Append(os.ErrPermission)) + assert.False(merr.Append(nil)) + }) + } +} + +func TestMultiErr_Flatten(t *testing.T) { + errs := map[string]func() MultiErr{ + "unsafe": NewMultiErr, + "safe": NewMultiErrSafe, + } + for name := range errs { + t.Run(name, func(t *testing.T) { + assert := asst.New(t) + merr := errs[name]() + merr.Append(os.ErrPermission) + merr.Append(os.ErrClosed) + assert.Equal(2, len(merr.Errors())) + + merr2 := errs[name]() + merr2.Append(merr) + merr2.Append(os.ErrNotExist) + merr2.Append(nil) // nil is not appended + assert.Equal(3, len(merr2.Errors())) + t.Log(merr2.Error()) + }) + } +} + +func TestMultiErr_ErrorOrNil(t *testing.T) { + assert := asst.New(t) + + merr := NewMultiErr() + assert.Nil(merr.ErrorOrNil()) + + merr.Append(os.ErrPermission) + assert.NotNil(merr.ErrorOrNil()) +} diff --git a/errors/pkg.go b/errors/pkg.go new file mode 100644 index 0000000..902fa5a --- /dev/null +++ b/errors/pkg.go @@ -0,0 +1,25 @@ +// Package errors provides multi error, error wrapping. It defines error category code for machine post processing +package errors + +import "fmt" + +const ( + // MultiErrSep is the separator used when returning a slice of errors as single line message + MultiErrSep = "; " + // ErrCauseSep is the separator used when returning a error with causal chain as single line message + ErrCauseSep = ": " +) + +func New(msg string) error { + return &FreshError{ + msg: msg, + stack: callers(), + } +} + +func Errorf(format string, args ...interface{}) error { + return &FreshError{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} diff --git a/errors/stack.go b/errors/stack.go new file mode 100644 index 0000000..81a4516 --- /dev/null +++ b/errors/stack.go @@ -0,0 +1,67 @@ +package errors + +import ( + "fmt" + "runtime" +) + +// TODO: it should be configurable? or move to pkg.go? +const depth = 20 + +type Stack struct { + p *runtime.Frames + depth int + frames []runtime.Frame +} + +func (s *Stack) Frames() []runtime.Frame { + if s == nil || s.p == nil { + return nil + } + if len(s.frames) != 0 { + return s.frames + } + frames := make([]runtime.Frame, 0, s.depth) + for { + frame, more := s.p.Next() + frames = append(frames, frame) + if !more { + break + } + } + s.frames = frames + return frames +} + +// TODO: handling print stack + +func callers() *Stack { + pcs := make([]uintptr, depth) + // 3 skips runtime.Callers itself, callers function, and the function that creates error, i.e New, Errorf + //more true | runtime.Callers /home/at15/app/go/src/runtime/extern.go:212 + //more true | github.com/dyweb/gommon/errors.callers /home/at15/workspace/src/github.com/dyweb/gommon/errors/stack.go:18 + //more true | github.com/dyweb/gommon/errors.New /home/at15/workspace/src/github.com/dyweb/gommon/errors/pkg.go:16 + n := runtime.Callers(3, pcs) + frames := runtime.CallersFrames(pcs[:n]) + return &Stack{ + p: frames, + depth: n, + } +} + +func printFrames(frames []runtime.Frame) { + for i := 0; i < len(frames); i++ { + fmt.Printf("%s %s:%d\n", frames[i].Function, frames[i].File, frames[i].Line) + } +} + +func printFramesPtr(frames *runtime.Frames) { + // from https://golang.org/pkg/runtime/#Frames + for { + frame, more := frames.Next() + fmt.Printf("more %v | %s %s:%d\n", more, frame.Function, frame.File, frame.Line) + if !more { + break + } + } +} diff --git a/errors/stack_test.go b/errors/stack_test.go new file mode 100644 index 0000000..eff4f2a --- /dev/null +++ b/errors/stack_test.go @@ -0,0 +1,16 @@ +package errors + +import ( + "testing" + + asst "github.com/stretchr/testify/assert" +) + +func TestStack_Frames(t *testing.T) { + assert := asst.New(t) + s := callers() + // when Frames() is not called, it is empty + assert.Equal(0, len(s.frames)) + printFrames(s.Frames()) + assert.Equal(s.depth, len(s.Frames())) +} diff --git a/errors/types.go b/errors/types.go new file mode 100644 index 0000000..71aac4c --- /dev/null +++ b/errors/types.go @@ -0,0 +1,10 @@ +package errors + +import "runtime" + +// TODO: error types, user, dev, std, lib, grpc etc. + +func IsRuntimeError(err error) bool { + _, ok := err.(runtime.Error) + return ok +} diff --git a/generator/config.go b/generator/config.go index b8fd0de..96787db 100644 --- a/generator/config.go +++ b/generator/config.go @@ -5,7 +5,7 @@ import ( "fmt" "go/format" - "github.com/pkg/errors" + "github.com/dyweb/gommon/errors" ) type Config struct { @@ -25,6 +25,7 @@ func (c *Config) RenderGommon() ([]byte, error) { body := &bytes.Buffer{} header := &bytes.Buffer{} fmt.Fprintf(header, Header(generatorName, c.file)) + fmt.Fprint(header, "\n") fmt.Fprintf(header, "package %s\n\n", c.pkg) if len(c.Loggers) > 0 { fmt.Fprintln(header, "import dlog \"github.com/dyweb/gommon/log\"") diff --git a/generator/generate.go b/generator/generate.go index 6262558..6e5a07f 100644 --- a/generator/generate.go +++ b/generator/generate.go @@ -6,8 +6,8 @@ import ( "strings" "github.com/dyweb/gommon/config" + "github.com/dyweb/gommon/errors" "github.com/dyweb/gommon/util/fsutil" - "github.com/pkg/errors" ) func Generate(root string) error { @@ -39,27 +39,27 @@ func GenerateSingle(file string) error { cfg := NewConfig(pkg, file) // TODO: config may remove LoadYAMLAsStruct in the future if err = config.LoadYAMLAsStruct(file, &cfg); err != nil { - return errors.WithMessage(err, "can't read config file") + return errors.Wrap(err, "can't read config file") } // gommon logger if rendered, err = cfg.RenderGommon(); err != nil { - return errors.WithMessage(err, "can't render based on config") + return errors.Wrap(err, "can't render based on config") } //log.Debugf("%s rendered length %d", file, len(rendered)) if err = fsutil.WriteFile(join(dir, generatedFile), rendered); err != nil { - return errors.WithMessage(err, "can't write rendered gommon file") + return errors.Wrap(err, "can't write rendered gommon file") } log.Debugf("generated %s from %s", join(dir, generatedFile), file) // gotmpl if err = cfg.RenderGoTemplate(dir); err != nil { - return errors.WithMessage(err, "can't render go templates") + return errors.Wrap(err, "can't render go templates") } // logger if err = cfg.RenderShell(dir); err != nil { - return errors.WithMessage(err, "can't render using shell commands") + return errors.Wrap(err, "can't render using shell commands") } return nil diff --git a/generator/gotmpl.go b/generator/gotmpl.go index df3e6b1..d33c7db 100644 --- a/generator/gotmpl.go +++ b/generator/gotmpl.go @@ -6,8 +6,7 @@ import ( "io/ioutil" "text/template" - "github.com/pkg/errors" - + "github.com/dyweb/gommon/errors" "github.com/dyweb/gommon/util/fsutil" ) @@ -33,6 +32,7 @@ func (c *GoTemplateConfig) Render(root string) error { return errors.Wrap(err, "can't parse template") } buf.WriteString(Header(generatorName, join(root, c.Src))) + buf.Write([]byte("\n")) if err = t.Execute(&buf, c.Data); err != nil { return errors.Wrap(err, "can't render template") } diff --git a/generator/logger.go b/generator/logger.go index 8537f8d..16f0936 100644 --- a/generator/logger.go +++ b/generator/logger.go @@ -1,9 +1,10 @@ package generator import ( - "github.com/pkg/errors" "io" "text/template" + + "github.com/dyweb/gommon/errors" ) type LoggerConfig struct { diff --git a/generator/pkg.go b/generator/pkg.go index f6651f2..a4cb18e 100644 --- a/generator/pkg.go +++ b/generator/pkg.go @@ -1,3 +1,4 @@ +// Package generator render go template, call external commands, generate gommon specific methods based on gommon.yml package generator import ( diff --git a/generator/shell.go b/generator/shell.go index 21c6da3..0d0dd84 100644 --- a/generator/shell.go +++ b/generator/shell.go @@ -4,9 +4,10 @@ import ( "os" "os/exec" - "github.com/dyweb/gommon/util/fsutil" "github.com/kballard/go-shellquote" - "github.com/pkg/errors" + + "github.com/dyweb/gommon/errors" + "github.com/dyweb/gommon/util/fsutil" ) // https://github.com/dyweb/gommon/issues/53 diff --git a/legacy/log/doc.go b/legacy/log/doc.go index 633a432..54c3c7b 100644 --- a/legacy/log/doc.go +++ b/legacy/log/doc.go @@ -1,5 +1,5 @@ /* -Package log can filter log by field and support multiple level +Package log(Deprecated) can filter log by field and support multiple level */ package log diff --git a/legacy/pkg.go b/legacy/pkg.go new file mode 100644 index 0000000..74470c7 --- /dev/null +++ b/legacy/pkg.go @@ -0,0 +1,2 @@ +// Package legacy contains deprecated gommon packages +package legacy diff --git a/legacy/runner/doc.go b/legacy/runner/doc.go index d31d5d2..f1e4a1f 100644 --- a/legacy/runner/doc.go +++ b/legacy/runner/doc.go @@ -1,4 +1,4 @@ /* -Package runner run commands with some convention +Package runner(Deprecated) run commands with some convention */ package runner diff --git a/log/gommon_generated.go b/log/gommon_generated.go index fa0689f..2f1d6b1 100644 --- a/log/gommon_generated.go +++ b/log/gommon_generated.go @@ -1,2 +1,3 @@ // Code generated by gommon from log/gommon.yml DO NOT EDIT. + package log diff --git a/log/logger_generated.go b/log/logger_generated.go index 666b4f1..8bb059b 100644 --- a/log/logger_generated.go +++ b/log/logger_generated.go @@ -1,4 +1,5 @@ // Code generated by gommon from log/logger_generated.go.tmpl DO NOT EDIT. + package log import ( diff --git a/log/pkg.go b/log/pkg.go index 704787b..0382132 100644 --- a/log/pkg.go +++ b/log/pkg.go @@ -1,4 +1,4 @@ -// Package log is not usable yet, see legacy/log +// Package log is a structured logging with fine grained control package log //// TODO: deal w/ http access log later diff --git a/noodle/fs_embed.go b/noodle/fs_embed.go index 78deb23..325c906 100644 --- a/noodle/fs_embed.go +++ b/noodle/fs_embed.go @@ -1,14 +1,14 @@ package noodle import ( + "archive/zip" + "bytes" + "io/ioutil" "net/http" "os" "time" - "archive/zip" - "bytes" - "github.com/pkg/errors" - "io/ioutil" + "github.com/dyweb/gommon/errors" ) var registeredBoxes map[string]EmbedBox diff --git a/noodle/fs_embed_generate.go b/noodle/fs_embed_generate.go index 3e1c747..80f60fb 100644 --- a/noodle/fs_embed_generate.go +++ b/noodle/fs_embed_generate.go @@ -10,8 +10,8 @@ import ( "strings" "text/template" + "github.com/dyweb/gommon/errors" "github.com/dyweb/gommon/util/fsutil" - "github.com/pkg/errors" ) // GenerateEmbed return code without package and the CODE GENERATED header @@ -22,7 +22,7 @@ func GenerateEmbed(root string) ([]byte, error) { dirs = make(map[string]*EmbedDir) files = make(map[string][]*EmbedFile) data []byte - lastErr error + merr = errors.NewMultiErr() ) if rootStat, err := os.Stat(root); err != nil { return nil, errors.Wrap(err, "can't get stat of root folder") @@ -46,16 +46,14 @@ func GenerateEmbed(root string) ([]byte, error) { dirs[path].Entries = append(dirs[path].Entries, dirInfo.FileInfo) return } - // TODO: error group if file, err := newEmbedFile(path, info); err != nil { - log.Warn(err) - lastErr = err + merr.Append(err) } else { files[path] = append(files[path], file) } }) - if lastErr != nil { - return nil, lastErr + if merr.HasError() { + return nil, merr } //log.Infof("dirs (including root) %d", len(dirs)) //log.Infof("dirs (excluding root) %d", len(files)) @@ -122,8 +120,7 @@ func updateDirectoryInfo(dirs map[string]*EmbedDir, flatFiles map[string][]*Embe } func zipFiles(root string, flatFiles map[string][]*EmbedFile) ([]byte, error) { - // TODO: error group - var lastErr error + merr := errors.NewMultiErr() buf := &bytes.Buffer{} w := zip.NewWriter(buf) // sort dir to make the output stable https://github.com/dyweb/gommon/issues/52 @@ -135,11 +132,11 @@ func zipFiles(root string, flatFiles map[string][]*EmbedFile) ([]byte, error) { for _, path := range paths { for _, f := range flatFiles[path] { //log.Infof("write file %s FileSize %d", f.FileName, len(f.Data)) - lastErr = writeZipFile(w, root, path, f) + merr.Append(writeZipFile(w, root, path, f)) } } - if lastErr != nil { - return nil, lastErr + if merr.HasError() { + return nil, merr } if err := w.Close(); err != nil { return nil, errors.Wrap(err, "can't close zip writer") diff --git a/noodle/ignore.go b/noodle/ignore.go index ce2fb0f..5ef6f0c 100644 --- a/noodle/ignore.go +++ b/noodle/ignore.go @@ -7,8 +7,8 @@ import ( "path/filepath" "strings" + "github.com/dyweb/gommon/errors" "github.com/dyweb/gommon/util/fsutil" - "github.com/pkg/errors" ) func ReadIgnoreFile(path string) (*fsutil.Ignores, error) { diff --git a/noodle/pkg.go b/noodle/pkg.go index 679932a..e63eca0 100644 --- a/noodle/pkg.go +++ b/noodle/pkg.go @@ -1,3 +1,4 @@ +// Package noodle helps embedding static assets into go binary, it supports using ignore file package noodle import ( diff --git a/playground/issue_noodle_50/main.go b/playground/issue_noodle_50/main.go index 2e6a1a4..3e637ae 100644 --- a/playground/issue_noodle_50/main.go +++ b/playground/issue_noodle_50/main.go @@ -1,3 +1,4 @@ +// for i, ele := range slice is reusing same struct https://github.com/dyweb/gommon/issues/50 package main import "fmt" diff --git a/playground/pkg.go b/playground/pkg.go new file mode 100644 index 0000000..19c4c27 --- /dev/null +++ b/playground/pkg.go @@ -0,0 +1,2 @@ +// Package playground is used for testing out issues and language features +package playground diff --git a/requests/README.md b/requests/README.md index 34b2b23..6a8fa0a 100644 --- a/requests/README.md +++ b/requests/README.md @@ -1,6 +1,6 @@ # Requests -A pythonic HTTP library for Gopher with socks5 proxy support +A wrapper around net/http with less public global variables Originally from [xephon-b](https://github.com/xephonhq/xephon-b) diff --git a/requests/auth.go b/requests/auth.go index db71c87..ce9e87a 100644 --- a/requests/auth.go +++ b/requests/auth.go @@ -5,7 +5,7 @@ import ( "net/http" "strings" - "github.com/pkg/errors" + "github.com/dyweb/gommon/errors" ) // NOTE: net/http already implemented it https://golang.org/pkg/net/http/#Request.BasicAuth @@ -34,7 +34,7 @@ func ExtractBasicAuth(val string) (username string, password string, err error) } ss := strings.Split(string(decoded), ":") if len(ss) != 2 { - errors.Errorf("invalid username:password, got %s segments after split by ':'", len(ss)) + errors.Errorf("invalid username:password, got %d segments after split by ':'", len(ss)) } username = ss[0] password = ss[1] diff --git a/requests/builder.go b/requests/builder.go index f33c2a7..c186264 100644 --- a/requests/builder.go +++ b/requests/builder.go @@ -2,9 +2,11 @@ package requests import ( "crypto/tls" - "github.com/pkg/errors" - "golang.org/x/net/proxy" "net/http" + + "golang.org/x/net/proxy" + + "github.com/dyweb/gommon/errors" ) // TODO: might switch to functional options https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis diff --git a/requests/pkg.go b/requests/pkg.go index 253560a..b08b15d 100644 --- a/requests/pkg.go +++ b/requests/pkg.go @@ -1,2 +1,2 @@ -// Package requests is a pythonic HTTP library for Gopher +// Package requests is a wrapper around net/http with less public global variables package requests diff --git a/requests/requests.go b/requests/requests.go index e161306..4c76787 100644 --- a/requests/requests.go +++ b/requests/requests.go @@ -9,7 +9,7 @@ import ( "net/http" "strings" - "github.com/pkg/errors" + "github.com/dyweb/gommon/errors" ) const ( diff --git a/requests/response.go b/requests/response.go index e9265d2..69162b9 100644 --- a/requests/response.go +++ b/requests/response.go @@ -4,7 +4,7 @@ import ( "encoding/json" "net/http" - "github.com/pkg/errors" + "github.com/dyweb/gommon/errors" ) type Response struct { diff --git a/structure/heap.go b/structure/heap.go index 6fe4564..57281cc 100644 --- a/structure/heap.go +++ b/structure/heap.go @@ -1 +1,3 @@ package structure + +// TODO diff --git a/structure/priority_queue.go b/structure/priority_queue.go index 6fe4564..57281cc 100644 --- a/structure/priority_queue.go +++ b/structure/priority_queue.go @@ -1 +1,3 @@ package structure + +// TODO diff --git a/util/color/color_generated.go b/util/color/color_generated.go index ebeefee..6924478 100644 --- a/util/color/color_generated.go +++ b/util/color/color_generated.go @@ -1,4 +1,5 @@ // Code generated by gommon from util/color/color_generated.go.tmpl DO NOT EDIT. + package color const ( diff --git a/util/color/gommon_generated.go b/util/color/gommon_generated.go index cd4ffb9..83ca45c 100644 --- a/util/color/gommon_generated.go +++ b/util/color/gommon_generated.go @@ -1,2 +1,3 @@ // Code generated by gommon from util/color/gommon.yml DO NOT EDIT. + package color diff --git a/util/color/pkg.go b/util/color/pkg.go new file mode 100644 index 0000000..9b3d354 --- /dev/null +++ b/util/color/pkg.go @@ -0,0 +1,2 @@ +// Package color defines color code for pretty print in linux shell, windows is not supported +package color diff --git a/util/fsutil/file.go b/util/fsutil/file.go index fe7fff1..64c8fda 100644 --- a/util/fsutil/file.go +++ b/util/fsutil/file.go @@ -3,7 +3,7 @@ package fsutil import ( "io/ioutil" - "github.com/pkg/errors" + "github.com/dyweb/gommon/errors" ) // WriteFile use 0666 as permission and wrap standard error diff --git a/util/fsutil/pkg.go b/util/fsutil/pkg.go index 695e564..73ec398 100644 --- a/util/fsutil/pkg.go +++ b/util/fsutil/pkg.go @@ -1,3 +1,4 @@ +// Package fsutil adds ignore support for walk package fsutil import ( diff --git a/util/fsutil/walk.go b/util/fsutil/walk.go index 06e97a3..a84f2dd 100644 --- a/util/fsutil/walk.go +++ b/util/fsutil/walk.go @@ -1,9 +1,10 @@ package fsutil import ( - "github.com/pkg/errors" "io/ioutil" "os" + + "github.com/dyweb/gommon/errors" ) type WalkFunc func(path string, info os.FileInfo) diff --git a/util/logutil/pkg.go b/util/logutil/pkg.go index 1857602..4181ec2 100644 --- a/util/logutil/pkg.go +++ b/util/logutil/pkg.go @@ -1,3 +1,4 @@ +// Package logutil is a registry of loggers, it is required for all lib and app that use gommon/log package logutil import ( diff --git a/util/pkg.go b/util/pkg.go index 7230150..efecf6b 100644 --- a/util/pkg.go +++ b/util/pkg.go @@ -1,4 +1,4 @@ -// Package util contains helper for test +// Package util contains helpers package util func GeneratedHeader(generator string, template string) string { diff --git a/util/runtimeutil/caller.go b/util/runtimeutil/caller.go index 2fac84a..5e36ab5 100644 --- a/util/runtimeutil/caller.go +++ b/util/runtimeutil/caller.go @@ -5,19 +5,6 @@ import ( "strings" ) -// GetCallerPackage is used by log package to get caller source code position -func GetCallerPackage(skip int) string { - pc, _, _, ok := runtime.Caller(skip) - if !ok { - return "unknown" - } - // FIXME: https://github.com/golang/go/issues/19426 use runtime.Frames instead of runtime.FuncForPC - fn := runtime.FuncForPC(pc) - fnName := fn.Name() - lastDot := strings.LastIndex(fnName, ".") - return fnName[:lastDot] -} - // see https://github.com/dyweb/gommon/issues/32 // based on https://github.com/go-stack/stack/blob/master/stack.go#L29:51 // TODO: not sure if calling two Next without checking the more value works for other go version @@ -68,3 +55,18 @@ func SplitStructMethod(f string) (st string, function string) { } return } + +// GetCallerPackage is used by log package to get caller source code position +// Deprecated +// only used by legacy/log +func GetCallerPackage(skip int) string { + pc, _, _, ok := runtime.Caller(skip) + if !ok { + return "unknown" + } + // FIXME: https://github.com/golang/go/issues/19426 use runtime.Frames instead of runtime.FuncForPC + fn := runtime.FuncForPC(pc) + fnName := fn.Name() + lastDot := strings.LastIndex(fnName, ".") + return fnName[:lastDot] +} diff --git a/util/runtimeutil/pkg.go b/util/runtimeutil/pkg.go new file mode 100644 index 0000000..ca6b070 --- /dev/null +++ b/util/runtimeutil/pkg.go @@ -0,0 +1,2 @@ +// Package runtimeutil provides wrapper to get caller, stack etc. +package runtimeutil diff --git a/util/testutil/pkg.go b/util/testutil/pkg.go new file mode 100644 index 0000000..d15937a --- /dev/null +++ b/util/testutil/pkg.go @@ -0,0 +1,2 @@ +// Package testutil defines helper functions that fails test instead of return error +package testutil