From d038ea52e19a0964d122d782dc8d8c6d1f80b427 Mon Sep 17 00:00:00 2001 From: Taras Postument Date: Sun, 25 Mar 2018 21:12:43 +0300 Subject: [PATCH 1/9] some initial implementation of dynamic custom width defining with reflection --- Gopkg.lock | 8 +- Gopkg.toml | 5 +- aws/tailers/stack_event.go | 151 +++++++---- aws/tailers/stack_event_test.go | 28 ++ .../ianlopshire/go-fixedwidth/.gitignore | 17 ++ .../ianlopshire/go-fixedwidth/LICENSE | 21 ++ .../ianlopshire/go-fixedwidth/README.md | 67 +++++ .../ianlopshire/go-fixedwidth/decode.go | 251 ++++++++++++++++++ .../ianlopshire/go-fixedwidth/decode_test.go | 185 +++++++++++++ .../ianlopshire/go-fixedwidth/encode.go | 228 ++++++++++++++++ .../ianlopshire/go-fixedwidth/encode_test.go | 128 +++++++++ .../ianlopshire/go-fixedwidth/fixedwidth.go | 2 + .../go-fixedwidth/fixedwidth_test.go | 31 +++ .../ianlopshire/go-fixedwidth/tags.go | 28 ++ .../ianlopshire/go-fixedwidth/tags_test.go | 44 +++ 15 files changed, 1146 insertions(+), 48 deletions(-) create mode 100644 aws/tailers/stack_event_test.go create mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/.gitignore create mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/LICENSE create mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/README.md create mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/decode.go create mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/decode_test.go create mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/encode.go create mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/encode_test.go create mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth.go create mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth_test.go create mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/tags.go create mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/tags_test.go diff --git a/Gopkg.lock b/Gopkg.lock index 85cefe67c..d84c77233 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -119,6 +119,12 @@ revision = "7f08801859139f86dfafd1c296e2cba9a80d292e" version = "v1.6.0" +[[projects]] + name = "github.com/ianlopshire/go-fixedwidth" + packages = ["."] + revision = "b5f5b05297fca5895c0c6d9b4c8dda091b84cfd6" + version = "v0.2.1" + [[projects]] name = "github.com/inconshreveable/mousetrap" packages = ["."] @@ -325,6 +331,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "c16569f26e1430b337ef16bbc9e19239de261ca85cc54b76ff67116e4eacabc7" + inputs-digest = "dc5a8334da384b8de743df843e031a98ecec1fcdcb02be3e7a9819584032bc1c" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index f1c55a485..2e00ad52b 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -75,4 +75,7 @@ [[constraint]] branch = "master" - name = "github.com/boombuler/barcode" \ No newline at end of file + name = "github.com/boombuler/barcode" +[[constraint]] + name = "github.com/ianlopshire/go-fixedwidth" + version = "0.2.1" diff --git a/aws/tailers/stack_event.go b/aws/tailers/stack_event.go index 461f57e97..7c0d35c10 100644 --- a/aws/tailers/stack_event.go +++ b/aws/tailers/stack_event.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "reflect" "strings" "text/tabwriter" "time" @@ -11,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/configservice" "github.com/fatih/color" + fixedwidth "github.com/ianlopshire/go-fixedwidth" "github.com/wallix/awless/aws/services" ) @@ -42,8 +44,20 @@ type stackEventTailer struct { cancelAfterTimeout bool } +// 53 - symbols, longest CF resource name: AWS::KinesisAnalytics::ApplicationReferenceDataSource +// 45 - longest CF statuse "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS" + +// Copy of cloudformation.StackEvent to set custom width for each field type stackEvent struct { - *cloudformation.StackEvent + // *cloudformation.StackEvent + Timestamp *time.Time + ResourceStatus *string + ResourceType *string + LogicalResourceId *string + + PhysicalResourceId *string + ResourceStatusReason *string + EventId *string } type stackEvents []stackEvent @@ -74,16 +88,14 @@ func (t *stackEventTailer) Tail(w io.Writer) error { return fmt.Errorf("invalid polling frequency: %s, must be greater than 5s", t.pollingFrequency) } - tab := tabwriter.NewWriter(w, 8, 8, 8, '\t', 0) - tab.Write(t.filters.header()) + // tab := tabwriter.NewWriter(w, 8, 8, 8, '\t', 0) + // tab.Write(t.filters.header()) if !t.follow { - if err := t.displayLastEvents(cfn, tab); err != nil { + if err := t.displayLastEvents(cfn, w); err != nil { return err } - tab.Flush() - return nil } @@ -118,12 +130,10 @@ func (t *stackEventTailer) Tail(w io.Writer) error { return fmt.Errorf("Timeout (%s) reached. Exiting...", t.timeout.String()) } case <-ticker.C: - if err := t.displayRelevantEvents(cfn, tab); err != nil { + if err := t.displayRelevantEvents(cfn, w); err != nil { return err } - tab.Flush() - if t.deploymentStatus.isFinished { if len(t.deploymentStatus.failedEvents) > 0 { var errBuf bytes.Buffer @@ -175,7 +185,7 @@ func (t *stackEventTailer) getLatestEvents(cfn *awsservices.Cloudformation) (sta if t.lastEventID != nil && *e.EventId == *t.lastEventID { return stEvents, nil } - stEvents = append(stEvents, stackEvent{e}) + stEvents = append(stEvents, NewStackEvent(e)) } if resp.NextToken == nil { @@ -233,7 +243,7 @@ func (t *stackEventTailer) getRelevantEvents(cfn *awsservices.Cloudformation) (s } for _, e := range resp.StackEvents { - event := stackEvent{e} + event := NewStackEvent(e) // if lastEventID == nil then it's first run of this method // if lastEventID == nil then it's not first run and print only new messages if t.lastEventID != nil && *e.EventId == *t.lastEventID { @@ -282,25 +292,28 @@ func (t *stackEventTailer) displayRelevantEvents(cfn *awsservices.Cloudformation return events.printReverse(w, t.filters) } -func coloredResourceStatus(str string) string { +func colorizeResourceStatus(str string) *string { + var c color.Attribute switch { case strings.HasSuffix(str, StackEventFailed), str == cloudformation.StackStatusUpdateRollbackInProgress, str == cloudformation.StackStatusRollbackInProgress: - return color.New(color.FgRed).SprintFunc()(str) + c = color.FgRed case strings.HasSuffix(str, StackEventInProgress): - return color.New(color.FgYellow).SprintFunc()(str) + c = color.FgYellow case strings.HasSuffix(str, StackEventComplete): - return color.New(color.FgGreen).SprintFunc()(str) - default: - return str + c = color.FgGreen } + s := color.New(c).SprintFunc()(str) + + return &s } func (e stackEvents) printReverse(w io.Writer, f filters) error { for i := len(e) - 1; i >= 0; i-- { - w.Write(e[i].filter(f)) + w.Write(e[i].Format(f)) + w.Write([]byte("\n")) } return nil @@ -333,34 +346,6 @@ func (f filters) header() []byte { return []byte(color.New(color.Bold).Sprintf(buf.String()) + "\n") } -func (e *stackEvent) filter(filters []string) (out []byte) { - var buf bytes.Buffer - - for i, f := range filters { - switch { - case f == StackEventLogicalID && e.LogicalResourceId != nil: - buf.WriteString(*e.LogicalResourceId) - case f == StackEventTimestamp && e.Timestamp != nil: - buf.WriteString(e.Timestamp.Format(time.RFC3339)) - case f == StackEventStatus && e.ResourceStatus != nil: - buf.WriteString(coloredResourceStatus(*e.ResourceStatus)) - case f == StackEventStatusReason && e.ResourceStatusReason != nil: - buf.WriteString(*e.ResourceStatusReason) - case f == StackEventType && e.ResourceType != nil: - buf.WriteString(*e.ResourceType) - } - - if i != len(filters)-1 { - buf.WriteRune('\t') - } - - } - - buf.WriteRune('\n') - - return buf.Bytes() -} - func (s *stackEvent) isDeploymentStart() bool { return (s.ResourceType != nil && *s.ResourceType == configservice.ResourceTypeAwsCloudFormationStack) && (s.ResourceStatus != nil && @@ -380,8 +365,82 @@ func (s *stackEvent) isFailed() bool { return (s.ResourceStatus != nil && (strings.HasSuffix(*s.ResourceStatus, StackEventFailed) || *s.ResourceStatus == cloudformation.StackStatusUpdateRollbackInProgress)) } +func (s *stackEvent) fromCFEvent() bool { + return (s.ResourceStatus != nil && (strings.HasSuffix(*s.ResourceStatus, StackEventFailed) || *s.ResourceStatus == cloudformation.StackStatusUpdateRollbackInProgress)) +} + func (s *stackEventTailer) cancelStackUpdate(cfn *awsservices.Cloudformation) error { inp := &cloudformation.CancelUpdateStackInput{StackName: &s.stackName} _, err := cfn.CancelUpdateStack(inp) return err } + +func NewStackEvent(e *cloudformation.StackEvent) stackEvent { + return stackEvent{ + Timestamp: e.Timestamp, + ResourceStatus: colorizeResourceStatus(*e.ResourceStatus), + ResourceType: e.ResourceType, + LogicalResourceId: e.LogicalResourceId, + PhysicalResourceId: e.PhysicalResourceId, + ResourceStatusReason: e.ResourceStatusReason, + EventId: e.EventId, + } +} + +func (e *stackEvent) Format(fil filters) []byte { + st := reflect.TypeOf(e).Elem() + sv := reflect.ValueOf(e).Elem() + var startPos = 1 + var fs []reflect.StructField + + for i := 0; i < st.NumField(); i++ { + fs = append(fs, st.Field(i)) + } + + for _, fil := range fil { + for i := 0; i < len(fs); i++ { + tag := e.getFieldPosition(fs[i].Name, &startPos, fil) + if tag == nil { + continue + } + fs[i].Tag = reflect.StructTag(*tag) + } + } + + st2 := reflect.StructOf(fs) + sv2 := sv.Convert(st2) + + b, _ := fixedwidth.Marshal(sv2.Interface()) + return b +} + +func (e *stackEvent) getFieldPosition(field string, startPos *int, f string) *string { + const space = 5 + var endPos = *startPos + + // for _, f := range filters { + switch { + case f == StackEventLogicalID && e.LogicalResourceId != nil && field == "LogicalResourceId": + endPos = *startPos + 20 + space + case f == StackEventTimestamp && e.Timestamp != nil && field == "Timestamp": + endPos = *startPos + 20 + space + case f == StackEventStatus && e.ResourceStatus != nil && field == "ResourceStatus": + endPos = *startPos + 53 + space + case f == StackEventStatusReason && e.ResourceStatusReason != nil && field == "ResourceStatusReason": + endPos = *startPos + 60 + space + case f == StackEventType && e.ResourceType != nil && field == "ResourceType": + endPos = *startPos + 45 + space + default: + return nil + } + // } + + // // field is missing from filter + // if endPos == *startPos { + // return "" + // } + + tag := fmt.Sprintf(`fixed:"%d,%d"`, *startPos, endPos) + *startPos = endPos + 1 + return &tag +} diff --git a/aws/tailers/stack_event_test.go b/aws/tailers/stack_event_test.go new file mode 100644 index 000000000..c7dc6f3e7 --- /dev/null +++ b/aws/tailers/stack_event_test.go @@ -0,0 +1,28 @@ +package awstailers + +import ( + "reflect" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudformation" +) + +type stackEventTest struct { + *cloudformation.StackEvent +} + +func TestReflect(t *testing.T) { + a := stackEventTest{ + &cloudformation.StackEvent{ + EventId: aws.String("sdasda"), + ResourceStatus: aws.String("dsada"), + }, + } + v, _ := reflect.TypeOf(a).Elem().FieldByName("EventId") + + v.Tag = reflect.StructTag(`fixed:"12,22"`) + + t.Log(v.Tag.Get("fixed")) + // t.Log(v.FieldByName("type")) +} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/.gitignore b/vendor/github.com/ianlopshire/go-fixedwidth/.gitignore new file mode 100644 index 000000000..e4e8855cf --- /dev/null +++ b/vendor/github.com/ianlopshire/go-fixedwidth/.gitignore @@ -0,0 +1,17 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ + +# JetBrains IDE related files +.idea/ \ No newline at end of file diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/LICENSE b/vendor/github.com/ianlopshire/go-fixedwidth/LICENSE new file mode 100644 index 000000000..7fa2906e3 --- /dev/null +++ b/vendor/github.com/ianlopshire/go-fixedwidth/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Ian Lopshire + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/README.md b/vendor/github.com/ianlopshire/go-fixedwidth/README.md new file mode 100644 index 000000000..246d22e0a --- /dev/null +++ b/vendor/github.com/ianlopshire/go-fixedwidth/README.md @@ -0,0 +1,67 @@ +# fixedwidth [![GoDoc](https://godoc.org/github.com/ianlopshire/go-fixedwidth?status.svg)](http://godoc.org/github.com/ianlopshire/go-fixedwidth) [![Report card](https://goreportcard.com/badge/github.com/ianlopshire/go-fixedwidth)](https://goreportcard.com/report/github.com/ianlopshire/go-fixedwidth) [![Go Cover](http://gocover.io/_badge/github.com/ianlopshire/go-fixedwidth)](http://gocover.io/github.com/ianlopshire/go-fixedwidth) + +Package fixedwidth provides encoding and decoding for fixed-width formatted Data. + +`go get github.com/ianlopshire/go-fixedwidth` + +## Usage + +### Struct Tags +Position within a line is controlled via struct tags. +The tags should be formatted as `fixed:"{startPos},{endPos}"` where `startPos` and `endPos` are both positive integers greater than 0. +Positions start at 1. The interval is inclusive. Fields without tags are ignored. + +### Encode +```go +// define some data to encode +people := []struct { + ID int `fixed:"1,5"` + FirstName string `fixed:"6,15"` + LastName string `fixed:"16,25"` + Grade float64 `fixed:"26,30"` +}{ + {1, "Ian", "Lopshire", 99.5}, +} + +data, err := fixedwidth.Marshal(people) +if err != nil { + log.Fatal(err) +} +fmt.Printf("%s", data) +// Output: +// 1 Ian Lopshire 99.50 +``` + +### Decode +```go +// define the format +var people []struct { + ID int `fixed:"1,5"` + FirstName string `fixed:"6,15"` + LastName string `fixed:"16,25"` + Grade float64 `fixed:"26,30"` +} + +// define some fixed-with data to parse +data := []byte("" + + "1 Ian Lopshire 99.50" + "\n" + + "2 John Doe 89.50" + "\n" + + "3 Jane Doe 79.50" + "\n") + + +err := fixedwidth.Unmarshal(data, &people) +if err != nil { + log.Fatal(err) +} + +fmt.Printf("%+v\n", people[0]) +fmt.Printf("%+v\n", people[1]) +fmt.Printf("%+v\n", people[2]) +// Output: +//{ID:1 FirstName:Ian LastName:Lopshire Grade:99.5} +//{ID:2 FirstName:John LastName:Doe Grade:89.5} +//{ID:3 FirstName:Jane LastName:Doe Grade:79.5} +``` + +## Licence +MIT diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/decode.go b/vendor/github.com/ianlopshire/go-fixedwidth/decode.go new file mode 100644 index 000000000..77e82bb86 --- /dev/null +++ b/vendor/github.com/ianlopshire/go-fixedwidth/decode.go @@ -0,0 +1,251 @@ +package fixedwidth + +import ( + "bufio" + "bytes" + "encoding" + "errors" + "io" + "reflect" + "strconv" +) + +// Unmarshal parses fixed width encoded data and stores the +// result in the value pointed to by v. If v is nil or not a +// pointer, Unmarshal returns an InvalidUnmarshalError. +func Unmarshal(data []byte, v interface{}) error { + return NewDecoder(bytes.NewReader(data)).Decode(v) +} + +// A Decoder reads and decodes fixed width data from an input stream. +type Decoder struct { + data *bufio.Reader + done bool +} + +// NewDecoder returns a new decoder that reads from r. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{ + data: bufio.NewReader(r), + } +} + +// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. +// (The argument to Unmarshal must be a non-nil pointer.) +type InvalidUnmarshalError struct { + Type reflect.Type +} + +func (e *InvalidUnmarshalError) Error() string { + if e.Type == nil { + return "fixedwidth: Unmarshal(nil)" + } + + if e.Type.Kind() != reflect.Ptr { + return "fixedwidth: Unmarshal(non-pointer " + e.Type.String() + ")" + } + return "fixedwidth: Unmarshal(nil " + e.Type.String() + ")" +} + +// An UnmarshalTypeError describes a value that was +// not appropriate for a value of a specific Go type. +type UnmarshalTypeError struct { + Value string // the raw value + Type reflect.Type // type of Go value it could not be assigned to + Struct string // name of the struct type containing the field + Field string // name of the field holding the Go value + Cause error // original error +} + +func (e *UnmarshalTypeError) Error() string { + var s string + if e.Struct != "" || e.Field != "" { + s = "fixedwidth: cannot unmarshal " + e.Value + " into Go struct field " + e.Struct + "." + e.Field + " of type " + e.Type.String() + } else { + s = "fixedwidth: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() + } + if e.Cause != nil { + return s + ":" + e.Cause.Error() + } + return s +} + +// Decode reads from its input and stores the decoded data to the value +// pointed to by v. +// +// In the case that v points to a struct value, Decode will read a +// single line from the input. +// +// In the case that v points to a slice value, Decode will read until +// the end of its input. +func (d *Decoder) Decode(v interface{}) error { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return &InvalidUnmarshalError{reflect.TypeOf(v)} + } + + if reflect.Indirect(reflect.ValueOf(v)).Kind() == reflect.Slice { + return d.readLines(reflect.ValueOf(v).Elem()) + } + return d.readLine(reflect.ValueOf(v)) +} + +func (d *Decoder) readLines(v reflect.Value) (err error) { + ct := v.Type().Elem() + for { + nv := reflect.New(ct).Elem() + err := d.readLine(nv) + if err != nil { + return err + } + if d.done { + break + } + v.Set(reflect.Append(v, nv)) + } + return nil +} + +func (d *Decoder) readLine(v reflect.Value) (err error) { + // TODO: properly handle prefixed lines + line, _, err := d.data.ReadLine() + if err == io.EOF { + d.done = true + return nil + } else if err != nil { + return err + } + + return newValueSetter(v.Type())(v, line) +} + +func rawValueFromLine(line []byte, startPos, endPos int) []byte { + if len(line) == 0 || startPos >= len(line) { + return []byte{} + } + if endPos > len(line) { + endPos = len(line) + } + return bytes.TrimSpace(line[startPos-1:endPos]) +} + +type valueSetter func(v reflect.Value, raw []byte) error + +var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() + +func newValueSetter(t reflect.Type) valueSetter { + if t.Implements(textUnmarshalerType) { + return textUnmarshalerSetter(t, false) + } + if reflect.PtrTo(t).Implements(textUnmarshalerType) { + return textUnmarshalerSetter(t, true) + } + + switch t.Kind() { + case reflect.Ptr: + return ptrSetter(t) + case reflect.Interface: + return interfaceSetter + case reflect.Struct: + return structSetter + case reflect.String: + return stringSetter + case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: + return intSetter + case reflect.Float32: + return floatSetter(32) + case reflect.Float64: + return floatSetter(64) + } + return unknownSetter +} + +func structSetter (v reflect.Value, raw []byte) error { + t := v.Type() + for i := 0; i < v.NumField(); i++ { + fv := v.Field(i) + if !fv.IsValid() { + continue + } + sf := t.Field(i) + startPos, endPos, ok := parseTag(sf.Tag.Get("fixed")) + if !ok { + continue + } + rawValue := rawValueFromLine(raw, startPos, endPos) + err := newValueSetter(sf.Type)(fv, rawValue) + if err != nil { + return &UnmarshalTypeError{string(rawValue), sf.Type, t.Name(), sf.Name, err} + } + } + return nil +} + +func unknownSetter(v reflect.Value, raw []byte) error { + return errors.New("fixedwidth: unknown type") +} + +func nilSetter(v reflect.Value, _ []byte) error { + v.Set(reflect.Zero(v.Type())) + return nil +} + +func textUnmarshalerSetter(t reflect.Type, shouldAddr bool) valueSetter { + return func(v reflect.Value, raw []byte) error { + if shouldAddr { + v = v.Addr() + } + // set to zero value if this is nil + if t.Kind() == reflect.Ptr && v.IsNil(){ + v.Set(reflect.New(t.Elem())) + } + return v.Interface().(encoding.TextUnmarshaler).UnmarshalText(raw) + } +} + +func interfaceSetter(v reflect.Value, raw []byte) error { + return newValueSetter(v.Elem().Type())(v.Elem(), raw) +} + +func ptrSetter(t reflect.Type) valueSetter { + return func(v reflect.Value, raw []byte) error { + if len(raw) <= 0 { + return nilSetter(v, raw) + } + if v.IsNil() { + v.Set(reflect.New(t.Elem())) + } + return newValueSetter(v.Elem().Type())(reflect.Indirect(v), raw) + } +} + +func stringSetter(v reflect.Value, raw []byte) error { + v.SetString(string(raw)) + return nil +} + +func intSetter(v reflect.Value, raw []byte) error { + if len(raw) < 1 { + return nil + } + i, err := strconv.Atoi(string(raw)) + if err != nil { + return err + } + v.SetInt(int64(i)) + return nil +} + +func floatSetter(bitSize int) valueSetter { + return func(v reflect.Value, raw []byte) error { + if len(raw) < 1 { + return nil + } + f, err := strconv.ParseFloat(string(raw), bitSize) + if err != nil { + return err + } + v.SetFloat(f) + return nil + } +} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/decode_test.go b/vendor/github.com/ianlopshire/go-fixedwidth/decode_test.go new file mode 100644 index 000000000..645a297f0 --- /dev/null +++ b/vendor/github.com/ianlopshire/go-fixedwidth/decode_test.go @@ -0,0 +1,185 @@ +package fixedwidth + +import ( + "fmt" + "log" + "reflect" + "testing" + "encoding" +) + +func ExampleUnmarshal() { + // define the format + var people []struct { + ID int `fixed:"1,5"` + FirstName string `fixed:"6,15"` + LastName string `fixed:"16,25"` + Grade float64 `fixed:"26,30"` + } + + // define some fixed-with data to parse + data := []byte("" + + "1 Ian Lopshire 99.50" + "\n" + + "2 John Doe 89.50" + "\n" + + "3 Jane Doe 79.50" + "\n") + + err := Unmarshal(data, &people) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("%+v\n", people[0]) + fmt.Printf("%+v\n", people[1]) + fmt.Printf("%+v\n", people[2]) + // Output: + //{ID:1 FirstName:Ian LastName:Lopshire Grade:99.5} + //{ID:2 FirstName:John LastName:Doe Grade:89.5} + //{ID:3 FirstName:Jane LastName:Doe Grade:79.5} +} + +func TestUnmarshal(t *testing.T) { + // allTypes contains a field with all current supported types. + type allTypes struct { + String string `fixed:"1,5"` + Int int `fixed:"6,10"` + Float float64 `fixed:"11,15"` + TextUnmarshaler EncodableString `fixed:"16,20"` + } + for _, tt := range []struct { + name string + rawValue []byte + target interface{} + expected interface{} + shouldErr bool + }{ + { + name: "Basic Slice Case", + rawValue: []byte("foo 123 1.2 bar" + "\n" + "bar 321 2.1 foo"), + target: &[]allTypes{}, + expected: &[]allTypes{ + {"foo", 123, 1.2, EncodableString{"bar", nil}}, + {"bar", 321, 2.1, EncodableString{"foo", nil}}, + }, + shouldErr: false, + }, + { + name: "Basic Struct Case", + rawValue: []byte("foo 123 1.2 bar"), + target: &allTypes{}, + expected: &allTypes{"foo", 123, 1.2, EncodableString{"bar", nil}}, + shouldErr: false, + }, + { + name: "Unmarshal Error", + rawValue: []byte("foo nan ddd bar"), + target: &allTypes{}, + expected: &allTypes{}, + shouldErr: true, + }, + { + name: "Empty Line", + rawValue: []byte(""), + target: &allTypes{}, + expected: &allTypes{"", 0, 0, EncodableString{"", nil}}, + shouldErr: false, + }, + { + name: "Invalid Target", + rawValue: []byte("foo 123 1.2 bar"), + target: allTypes{}, + expected: allTypes{}, + shouldErr: true, + }, + } { + t.Run(tt.name, func(t *testing.T) { + err := Unmarshal(tt.rawValue, tt.target) + if tt.shouldErr != (err != nil) { + t.Errorf("Unmarshal() err want %v, have %v (%v)", tt.shouldErr, err != nil, err.Error()) + } + if !tt.shouldErr && !reflect.DeepEqual(tt.target, tt.expected) { + t.Errorf("Unmarshal() want %+v, have %+v", tt.expected, tt.target) + } + + }) + } + + t.Run("Invalid Unmarshal Errors", func(t *testing.T) { + for _, tt := range []struct { + name string + v interface{} + shouldErr bool + }{ + {"Invalid Unmarshal Nil", nil, true}, + {"Invalid Unmarshal Not Pointer 1", struct{}{}, true}, + {"Invalid Unmarshal Not Pointer 2", []struct{}{}, true}, + {"Valid Unmarshal slice", &[]struct{}{}, false}, + {"Valid Unmarshal struct", &struct{}{}, false}, + } { + t.Run(tt.name, func(t *testing.T) { + err := Unmarshal([]byte{}, tt.v) + if tt.shouldErr != (err != nil) { + t.Errorf("Unmarshal() err want %v, have %v (%v)", tt.shouldErr, err != nil, err) + } + }) + } + }) +} + +func TestNewValueSetter(t *testing.T) { + for _, tt := range []struct { + name string + raw []byte + expected interface{} + shouldErr bool + }{ + {"invalid type", []byte("foo"), true, true}, + + {"textUnmarshaler implementation", []byte("foo"), &EncodableString{"foo", nil}, false}, + {"textUnmarshaler implementation if addressed", []byte("foo"), EncodableString{"foo", nil}, false}, + {"textUnmarshaler implementation as interface", []byte("foo"), encoding.TextUnmarshaler(&EncodableString{"foo", nil}), false}, + {"textUnmarshaler implementation in interface", []byte("foo"), interface{}(&EncodableString{"foo", nil}), false}, + {"textUnmarshaler implementation if addressed in interface", []byte("foo"), interface{}(EncodableString{"foo", nil}), false}, + + {"string", []byte("foo"), string("foo"), false}, + {"string empty", []byte(""), string(""), false}, + {"string interface", []byte("foo"), interface{}(string("foo")), false}, + {"string interface empty", []byte(""), interface{}(string("")), false}, + {"*string", []byte("foo"), stringp("foo"), false}, + {"*string empty", []byte(""), (*string)(nil), false}, + + {"int", []byte("1"), int(1), false}, + {"int zero", []byte("0"), int(0), false}, + {"int empty", []byte(""), int(0), false}, + {"*int", []byte("1"), intp(1), false}, + {"*int zero", []byte("0"), intp(0), false}, + {"*int empty", []byte(""), (*int)(nil), false}, + {"int Invalid", []byte("foo"), int(0), true}, + + {"float64", []byte("1.23"), float64(1.23), false}, + {"*float64", []byte("1.23"), float64p(1.23), false}, + {"*float64 zero", []byte("0"), float64p(0), false}, + {"*float64 empty", []byte(""), (*float64)(nil), false}, + {"float64 Invalid", []byte("foo"), float64(0), true}, + + {"float32", []byte("1.23"), float32(1.23), false}, + {"float32 Invalid", []byte("foo"), float32(0), true}, + + {"int8", []byte("1"), int8(1), false}, + {"int16", []byte("1"), int16(1), false}, + {"int32", []byte("1"), int32(1), false}, + {"int64", []byte("1"), int64(1), false}, + } { + t.Run(tt.name, func(t *testing.T) { + // ensure we have an addressable target + var i = reflect.Indirect(reflect.New(reflect.TypeOf(tt.expected))) + + err := newValueSetter(i.Type())(i, tt.raw) + if tt.shouldErr != (err != nil) { + t.Errorf("newValueSetter(%s)() err want %v, have %v (%v)", reflect.TypeOf(tt.expected).Name(), tt.shouldErr, err != nil, err.Error()) + } + if !tt.shouldErr && !reflect.DeepEqual(tt.expected, i.Interface()) { + t.Errorf("newValueSetter(%s)() want %s, have %s", reflect.TypeOf(tt.expected).Name(), tt.expected, i) + } + }) + } +} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/encode.go b/vendor/github.com/ianlopshire/go-fixedwidth/encode.go new file mode 100644 index 000000000..51c5907d5 --- /dev/null +++ b/vendor/github.com/ianlopshire/go-fixedwidth/encode.go @@ -0,0 +1,228 @@ +package fixedwidth + +import ( + "bufio" + "bytes" + "encoding" + "io" + "reflect" + "strconv" +) + +// Marshal returns the fixed-width encoding of v. +// +// v must be an encodable type or a slice of an encodable +// type. If v is a slice, each item will be treated as a +// line. If v is a single encodable type, a single line +// will be encoded. +// +// In order for a type to be encodable, it must implement +// the encoding.TextMarshaler interface or be based on one +// of the following builtin types: string, int, int64, +// int32, int16, int8, float64, float32, or struct. +// Pointers to encodable types and interfaces containing +// encodable types are also encodable. +// +// nil pointers and interfaces will be omitted. zero vales +// will be encoded normally. +// +// A struct is encoded to a single slice of bytes. Each +// field in a struct will be encoded and placed at the +// position defined by its struct tags. The tags should be +// formatted as `fixed:"{startPos},{endPos}"`. Positions +// start at 1. The interval is inclusive. Fields without +// tags and Fields of an un-encodable type are ignored. +// +// If the encoded value of a field is longer than the +// length of the position interval, the overflow is +// truncated. +func Marshal(v interface{}) ([]byte, error) { + buff := bytes.NewBuffer(nil) + err := NewEncoder(buff).Encode(v) + if err != nil { + return nil, err + } + return buff.Bytes(), nil +} + +// MarshalInvalidTypeError describes an invalid type being marshaled. +type MarshalInvalidTypeError struct { + typeName string +} + +func (e *MarshalInvalidTypeError) Error() string { + return "fixedwidth: cannot marshal unknown Type " + e.typeName +} + +// An Encoder writes fixed-width formatted data to an output +// stream. +type Encoder struct { + w *bufio.Writer +} + +// NewEncoder returns a new encoder that writes to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + bufio.NewWriter(w), + } +} + +// Encode writes the fixed-width encoding of v to the +// stream. +// See the documentation for Marshal for details about +// encoding behavior. +func (e *Encoder) Encode(i interface{}) (err error) { + if i == nil { + return nil + } + + // check to see if i should be encoded into multiple lines + v := reflect.ValueOf(i) + for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { + v = v.Elem() + } + if v.Kind() == reflect.Slice { + // encode each slice element to a line + err = e.writeLines(v) + } else { + // this is a single object so encode the original vale to a line + err = e.writeLine(reflect.ValueOf(i)) + } + if err != nil { + return err + } + return e.w.Flush() +} + +func (e *Encoder) writeLines(v reflect.Value) error { + for i := 0; i < v.Len(); i++ { + err := e.writeLine(v.Index(i)) + if err != nil { + return err + } + + if i != v.Len()-1 { + _, err := e.w.Write([]byte("\n")) + if err != nil { + return err + } + } + } + return nil +} + +func (e *Encoder) writeLine(v reflect.Value) (err error) { + b, err := newValueEncoder(v.Type())(v) + if err != nil { + return err + } + _, err = e.w.Write(b) + return err +} + +type valueEncoder func(v reflect.Value) ([]byte, error) + +func newValueEncoder(t reflect.Type) valueEncoder { + if t == nil { + return nilEncoder + } + if t.Implements(reflect.TypeOf(new(encoding.TextMarshaler)).Elem()) { + return textMarshalerEncoder + } + + switch t.Kind() { + case reflect.Ptr, reflect.Interface: + return ptrInterfaceEncoder + case reflect.Struct: + return structEncoder + case reflect.String: + return stringEncoder + case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: + return intEncoder + case reflect.Float64: + return floatEncoder(2, 64) + case reflect.Float32: + return floatEncoder(2, 32) + } + return unknownTypeEncoder(t) +} + +func structEncoder(v reflect.Value) ([]byte, error) { + var specs []fieldSpec + for i := 0; i < v.Type().NumField(); i++ { + f := v.Type().Field(i) + var ( + err error + spec fieldSpec + ok bool + ) + spec.startPos, spec.endPos, ok = parseTag(f.Tag.Get("fixed")) + if !ok { + continue + } + spec.value, err = newValueEncoder(f.Type)(v.Field(i)) + if err != nil { + return nil, err + } + specs = append(specs, spec) + } + return encodeSpecs(specs), nil +} + +type fieldSpec struct { + startPos, endPos int + value []byte +} + +func encodeSpecs(specs []fieldSpec) []byte { + var ll int + for _, spec := range specs { + if spec.endPos > ll { + ll = spec.endPos + } + } + data := bytes.Repeat([]byte(" "), ll) + for _, spec := range specs { + for i, b := range spec.value { + if spec.startPos+i <= spec.endPos { + data[spec.startPos+i-1] = b + } + } + } + return data +} + +func textMarshalerEncoder(v reflect.Value) ([]byte, error) { + return v.Interface().(encoding.TextMarshaler).MarshalText() +} + +func ptrInterfaceEncoder(v reflect.Value) ([]byte, error) { + if v.IsNil() { + return nilEncoder(v) + } + return newValueEncoder(v.Elem().Type())(v.Elem()) +} + +func stringEncoder(v reflect.Value) ([]byte, error) { + return []byte(v.String()), nil +} + +func intEncoder(v reflect.Value) ([]byte, error) { + return []byte(strconv.Itoa(int(v.Int()))), nil +} + +func floatEncoder(perc, bitSize int) valueEncoder { + return func(v reflect.Value) ([]byte, error) { + return []byte(strconv.FormatFloat(v.Float(), 'f', perc, bitSize)), nil + } +} + +func nilEncoder(v reflect.Value) ([]byte, error) { + return nil, nil +} + +func unknownTypeEncoder(t reflect.Type) valueEncoder { + return func(value reflect.Value) ([]byte, error) { + return nil, &MarshalInvalidTypeError{typeName: t.Name()} + } +} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/encode_test.go b/vendor/github.com/ianlopshire/go-fixedwidth/encode_test.go new file mode 100644 index 000000000..acf50d07f --- /dev/null +++ b/vendor/github.com/ianlopshire/go-fixedwidth/encode_test.go @@ -0,0 +1,128 @@ +package fixedwidth + +import ( + "bytes" + "fmt" + "github.com/pkg/errors" + "log" + "reflect" + "testing" +) + +func ExampleMarshal() { + // define some data to encode + people := []struct { + ID int `fixed:"1,5"` + FirstName string `fixed:"6,15"` + LastName string `fixed:"16,25"` + Grade float64 `fixed:"26,30"` + }{ + {1, "Ian", "Lopshire", 99.5}, + } + + data, err := Marshal(people) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%s", data) + // Output: + // 1 Ian Lopshire 99.50 +} + +func TestMarshal(t *testing.T) { + type H struct { + F1 interface{} `fixed:"1,5"` + F2 interface{} `fixed:"6,10"` + } + tagHelper := struct { + Valid string `fixed:"1,5"` + NoTags string + InvalidTags string `fixed:"5"` + }{"foo", "foo", "foo"} + marshalError := errors.New("marshal error") + + for _, tt := range []struct { + name string + i interface{} + o []byte + shouldErr bool + }{ + {"single line", H{"foo", 1}, []byte("foo 1 "), false}, + {"multiple line", []H{{"foo", 1}, {"bar", 2}}, []byte("foo 1 \nbar 2 "), false}, + {"empty slice", []H{}, nil, false}, + {"pointer", &H{"foo", 1}, []byte("foo 1 "), false}, + {"nil", nil, nil, false}, + {"invalid type", true, nil, true}, + {"invalid type in struct", H{"foo", true}, nil, true}, + {"marshal error", EncodableString{"", marshalError}, nil, true}, + {"invalid tags", tagHelper, []byte("foo "), false}, + } { + t.Run(tt.name, func(t *testing.T) { + o, err := Marshal(tt.i) + if tt.shouldErr != (err != nil) { + t.Errorf("Marshal() shouldErr expected %v, have %v (%v)", tt.shouldErr, err != nil, err) + } + if !tt.shouldErr && !bytes.Equal(o, tt.o) { + t.Errorf("Marshal() expected %s, have %s", tt.o, o) + } + }) + } +} + +func TestNewValueEncoder(t *testing.T) { + for _, tt := range []struct { + name string + i interface{} + o []byte + shouldErr bool + }{ + {"nil", nil, []byte(""), false}, + {"nil interface", interface{}(nil), []byte(""), false}, + + {"[]string (invalid)", []string{"a", "b"}, []byte(""), true}, + {"[]string interface (invalid)", interface{}([]string{"a", "b"}), []byte(""), true}, + {"bool (invalid)", true, []byte(""), true}, + + {"string", "foo", []byte("foo"), false}, + {"string interface", interface{}("foo"), []byte("foo"), false}, + {"string empty", "", []byte(""), false}, + {"*string", stringp("foo"), []byte("foo"), false}, + {"*string empty", stringp(""), []byte(""), false}, + {"*string nil", nilString, []byte(""), false}, + + {"float64", float64(123.4567), []byte("123.46"), false}, + {"float64 interface", interface{}(float64(123.4567)), []byte("123.46"), false}, + {"float64 zero", float64(0), []byte("0.00"), false}, + {"*float64", float64p(123.4567), []byte("123.46"), false}, + {"*float64 zero", float64p(0), []byte("0.00"), false}, + {"*float64 nil", nilFloat64, []byte(""), false}, + + {"float32", float32(123.4567), []byte("123.46"), false}, + {"float32 interface", interface{}(float32(123.4567)), []byte("123.46"), false}, + {"float32 zero", float32(0), []byte("0.00"), false}, + {"*float32", float32p(123.4567), []byte("123.46"), false}, + {"*float32 zero", float32p(0), []byte("0.00"), false}, + {"*float32 nil", nilFloat32, []byte(""), false}, + + {"int", int(123), []byte("123"), false}, + {"int interface", interface{}(int(123)), []byte("123"), false}, + {"int zero", int(0), []byte("0"), false}, + {"*int", intp(123), []byte("123"), false}, + {"*int zero", intp(0), []byte("0"), false}, + {"*int nil", nilInt, []byte(""), false}, + + {"TextUnmarshaler", EncodableString{"foo", nil}, []byte("foo"), false}, + {"TextUnmarshaler interface", interface{}(EncodableString{"foo", nil}), []byte("foo"), false}, + {"TextUnmarshaler error", EncodableString{"foo", errors.New("TextUnmarshaler error")}, []byte("foo"), true}, + } { + t.Run(tt.name, func(t *testing.T) { + o, err := newValueEncoder(reflect.TypeOf(tt.i))(reflect.ValueOf(tt.i)) + if tt.shouldErr != (err != nil) { + t.Errorf("newValueEncoder(%s)() shouldErr expected %v, have %v (%v)", reflect.TypeOf(tt.i).Name(), tt.shouldErr, err != nil, err) + } + if !tt.shouldErr && !bytes.Equal(o, tt.o) { + t.Errorf("newValueEncoder(%s)() expected %v, have %v", reflect.TypeOf(tt.i).Name(), tt.o, o) + } + }) + } +} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth.go b/vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth.go new file mode 100644 index 000000000..80992559d --- /dev/null +++ b/vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth.go @@ -0,0 +1,2 @@ +// Package fixedwidth provides encoding and decoding for fixed-width formatted Data. +package fixedwidth diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth_test.go b/vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth_test.go new file mode 100644 index 000000000..de51609a3 --- /dev/null +++ b/vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth_test.go @@ -0,0 +1,31 @@ +package fixedwidth + +var ( + nilFloat64 *float64 + nilFloat32 *float32 + nilInt *int + nilString *string +) + +func float64p(v float64) *float64 { return &v } +func float32p(v float32) *float32 { return &v } +func intp(v int) *int { return &v } +func stringp(v string) *string { return &v } + +// EncodableString is a string that implements the encoding TextUnmarshaler and TextMarshaler interface. +// This is useful for testing. +type EncodableString struct { + S string + Err error +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (s *EncodableString) UnmarshalText(text []byte) error { + s.S = string(text) + return s.Err +} + +// MarshalText implements encoding.TextUnmarshaler. +func (s EncodableString) MarshalText() ([]byte, error) { + return []byte(s.S), s.Err +} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/tags.go b/vendor/github.com/ianlopshire/go-fixedwidth/tags.go new file mode 100644 index 000000000..ba54e2de1 --- /dev/null +++ b/vendor/github.com/ianlopshire/go-fixedwidth/tags.go @@ -0,0 +1,28 @@ +package fixedwidth + +import ( + "strconv" + "strings" +) + +// parseTag splits a struct fields fixed tag into its start and end positions. +// If the tag is not valid, ok will be false. +func parseTag(tag string) (startPos, endPos int, ok bool) { + parts := strings.Split(tag, ",") + if len(parts) != 2 { + return startPos, endPos, false + } + + var err error + if startPos, err = strconv.Atoi(parts[0]); err != nil { + return startPos, endPos, false + } + if endPos, err = strconv.Atoi(parts[1]); err != nil { + return startPos, endPos, false + } + if startPos > endPos || (startPos == 0 && endPos == 0) { + return startPos, endPos, false + } + + return startPos, endPos, true +} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/tags_test.go b/vendor/github.com/ianlopshire/go-fixedwidth/tags_test.go new file mode 100644 index 000000000..4bea88c25 --- /dev/null +++ b/vendor/github.com/ianlopshire/go-fixedwidth/tags_test.go @@ -0,0 +1,44 @@ +package fixedwidth + +import ( + "testing" +) + +func TestParseTag(t *testing.T) { + for _, tt := range []struct { + name string + tag string + startPos int + endPos int + ok bool + }{ + {"Valid Tag", "0,10", 0, 10, true}, + {"Valid Tag Single position", "5,5", 5, 5, true}, + {"Tag Empty", "", 0, 0, false}, + {"Tag Too short", "0", 0, 0, false}, + {"Tag Too Long", "2,10,11", 0, 0, false}, + {"StartPos Not Integer", "hello,3", 0, 0, false}, + {"EndPos Not Integer", "3,hello", 0, 0, false}, + {"Tag Contains a Space", "4, 11", 0, 0, false}, + {"Tag Interval Invalid", "14,5", 0, 0, false}, + {"Tag Both Positions Zero", "0,0", 0, 0, false}, + } { + t.Run(tt.name, func(t *testing.T) { + startPos, endPos, ok := parseTag(tt.tag) + if tt.ok != ok { + t.Errorf("parseTag() ok want %v, have %v", tt.ok, ok) + } + + // only check startPos and endPos if valid tags are expected + if tt.ok { + if tt.startPos != startPos { + t.Errorf("parseTag() startPos want %v, have %v", tt.startPos, startPos) + } + + if tt.endPos != endPos { + t.Errorf("parseTag() endPos want %v, have %v", tt.endPos, endPos) + } + } + }) + } +} From 7b7cecaf062206d7248e23558d3d116410ad8ebd Mon Sep 17 00:00:00 2001 From: Taras Postument Date: Sun, 25 Mar 2018 23:59:45 +0300 Subject: [PATCH 2/9] add comments and headers --- aws/tailers/stack_event.go | 103 ++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 60 deletions(-) diff --git a/aws/tailers/stack_event.go b/aws/tailers/stack_event.go index 7c0d35c10..4d8e1c5f4 100644 --- a/aws/tailers/stack_event.go +++ b/aws/tailers/stack_event.go @@ -44,13 +44,9 @@ type stackEventTailer struct { cancelAfterTimeout bool } -// 53 - symbols, longest CF resource name: AWS::KinesisAnalytics::ApplicationReferenceDataSource -// 45 - longest CF statuse "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS" - -// Copy of cloudformation.StackEvent to set custom width for each field +// Copy of cloudformation.StackEvent for futher string formating type stackEvent struct { - // *cloudformation.StackEvent - Timestamp *time.Time + Timestamp *string ResourceStatus *string ResourceType *string LogicalResourceId *string @@ -88,15 +84,9 @@ func (t *stackEventTailer) Tail(w io.Writer) error { return fmt.Errorf("invalid polling frequency: %s, must be greater than 5s", t.pollingFrequency) } - // tab := tabwriter.NewWriter(w, 8, 8, 8, '\t', 0) - // tab.Write(t.filters.header()) - + w.Write(t.filters.header()) if !t.follow { - if err := t.displayLastEvents(cfn, w); err != nil { - return err - } - - return nil + return t.displayLastEvents(cfn, w) } isDeploying, err := t.isStackBeingDeployed(cfn) @@ -312,38 +302,23 @@ func colorizeResourceStatus(str string) *string { func (e stackEvents) printReverse(w io.Writer, f filters) error { for i := len(e) - 1; i >= 0; i-- { - w.Write(e[i].Format(f)) - w.Write([]byte("\n")) + w.Write(e[i].format(f)) } return nil } func (f filters) header() []byte { - var buf bytes.Buffer - for i, filter := range f { - switch filter { - case StackEventLogicalID: - buf.WriteString("Logical ID") - case StackEventTimestamp: - buf.WriteString("Timestamp") - case StackEventStatus: - buf.WriteString("Status") - case StackEventStatusReason: - buf.WriteString("Status Reason") - case StackEventType: - buf.WriteString("Type") - } - - if i != len(f)-1 { - buf.WriteRune('\t') - } - + s := &stackEvent{ + Timestamp: func() *string { t := "Timestamp"; return &t }(), + ResourceStatus: func() *string { t := "Status"; return &t }(), + LogicalResourceId: func() *string { t := "Logical ID"; return &t }(), + PhysicalResourceId: func() *string { t := "Physical ID"; return &t }(), + ResourceStatusReason: func() *string { t := "Status Reason"; return &t }(), + ResourceType: func() *string { t := "Type"; return &t }(), } - // with "\n" formatted with bold, tabwriter somehow shift lines - // so we need to add "\n" after string being bolded - return []byte(color.New(color.Bold).Sprintf(buf.String()) + "\n") + return s.format(f) } func (s *stackEvent) isDeploymentStart() bool { @@ -377,8 +352,8 @@ func (s *stackEventTailer) cancelStackUpdate(cfn *awsservices.Cloudformation) er func NewStackEvent(e *cloudformation.StackEvent) stackEvent { return stackEvent{ - Timestamp: e.Timestamp, - ResourceStatus: colorizeResourceStatus(*e.ResourceStatus), + Timestamp: func() *string { t := e.Timestamp.Format(time.RFC3339); return &t }(), + ResourceStatus: e.ResourceStatus, ResourceType: e.ResourceType, LogicalResourceId: e.LogicalResourceId, PhysicalResourceId: e.PhysicalResourceId, @@ -387,58 +362,66 @@ func NewStackEvent(e *cloudformation.StackEvent) stackEvent { } } -func (e *stackEvent) Format(fil filters) []byte { - st := reflect.TypeOf(e).Elem() - sv := reflect.ValueOf(e).Elem() +// Format dynamically generates fixed position for each field +// by adding struct tag like `fixed:"start,end"` for +// further marshaling into structured field by package +// "github.com/ianlopshire/go-fixedwidth" +func (s *stackEvent) format(fil filters) []byte { + st := reflect.TypeOf(s).Elem() + sv := reflect.ValueOf(s).Elem() var startPos = 1 var fs []reflect.StructField + // copying original struct fields for i := 0; i < st.NumField(); i++ { fs = append(fs, st.Field(i)) } + // applying tags to the struct fields based on + // provided filters values for _, fil := range fil { for i := 0; i < len(fs); i++ { - tag := e.getFieldPosition(fs[i].Name, &startPos, fil) + tag := s.getFieldPosition(fs[i].Name, &startPos, fil) if tag == nil { continue } fs[i].Tag = reflect.StructTag(*tag) } } - + // creating new structure based on it's fields st2 := reflect.StructOf(fs) + // copying values from original structure to the new one sv2 := sv.Convert(st2) + // marshalling struct to the string of fixed size + // based on applyed tags b, _ := fixedwidth.Marshal(sv2.Interface()) - return b + return append(b, []byte("\n")...) } -func (e *stackEvent) getFieldPosition(field string, startPos *int, f string) *string { +func (s *stackEvent) getFieldPosition(field string, startPos *int, f string) *string { const space = 5 var endPos = *startPos - // for _, f := range filters { switch { - case f == StackEventLogicalID && e.LogicalResourceId != nil && field == "LogicalResourceId": - endPos = *startPos + 20 + space - case f == StackEventTimestamp && e.Timestamp != nil && field == "Timestamp": + case f == StackEventLogicalID && s.LogicalResourceId != nil && field == "LogicalResourceId": endPos = *startPos + 20 + space - case f == StackEventStatus && e.ResourceStatus != nil && field == "ResourceStatus": + case f == StackEventTimestamp && s.Timestamp != nil && field == "Timestamp": + endPos = *startPos + 18 + space + case f == StackEventStatus && s.ResourceStatus != nil && field == "ResourceStatus": + s.ResourceStatus = colorizeResourceStatus(*s.ResourceStatus) + // 53 - symbols, longest CF resource name: "AWS::KinesisAnalytics::ApplicationReferenceDataSource" endPos = *startPos + 53 + space - case f == StackEventStatusReason && e.ResourceStatusReason != nil && field == "ResourceStatusReason": + case f == StackEventStatusReason && s.ResourceStatusReason != nil && field == "ResourceStatusReason": + // resource status reason, copping on 60 characters. complete error will + // be displayed as summary when the tailing complete endPos = *startPos + 60 + space - case f == StackEventType && e.ResourceType != nil && field == "ResourceType": + case f == StackEventType && s.ResourceType != nil && field == "ResourceType": + // 45 - longest CF status "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS" endPos = *startPos + 45 + space default: return nil } - // } - - // // field is missing from filter - // if endPos == *startPos { - // return "" - // } tag := fmt.Sprintf(`fixed:"%d,%d"`, *startPos, endPos) *startPos = endPos + 1 From caa44f73de2990a951e6eacf3432940aeb056ba1 Mon Sep 17 00:00:00 2001 From: Taras Postument Date: Sat, 31 Mar 2018 00:11:35 +0300 Subject: [PATCH 3/9] better realtime stack events tail formatting --- aws/tailers/stack_event.go | 166 ++++++++++++++++++++----------------- commands/tail.go | 10 +-- 2 files changed, 91 insertions(+), 85 deletions(-) diff --git a/aws/tailers/stack_event.go b/aws/tailers/stack_event.go index 4d8e1c5f4..cf3d57220 100644 --- a/aws/tailers/stack_event.go +++ b/aws/tailers/stack_event.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "reflect" + "strconv" "strings" "text/tabwriter" "time" @@ -12,16 +13,16 @@ import ( "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/configservice" "github.com/fatih/color" - fixedwidth "github.com/ianlopshire/go-fixedwidth" "github.com/wallix/awless/aws/services" ) const ( - StackEventLogicalID = "id" - StackEventTimestamp = "ts" - StackEventStatus = "status" - StackEventStatusReason = "reason" - StackEventType = "type" + StackEventFilterLogicalID = "id" + StackEventFilterTimestamp = "ts" + StackEventFilterStatus = "status" + StackEventFilterStatusReason = "reason" + StackEventFilterType = "type" + StackEventFilterPhysicalId = "physical-id" // valid stack status codes // http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-describing-stacks.html#w2ab2c15c15c17c11 @@ -46,16 +47,28 @@ type stackEventTailer struct { // Copy of cloudformation.StackEvent for futher string formating type stackEvent struct { - Timestamp *string - ResourceStatus *string - ResourceType *string - LogicalResourceId *string + Timestamp *string `width:"20,5"` + ResourceStatus *string `width:"50,5"` + ResourceType *string `width:"45,5"` + LogicalResourceId *string `width:"20,5"` - PhysicalResourceId *string - ResourceStatusReason *string + PhysicalResourceId *string `width:"100,5"` + ResourceStatusReason *string `width:"100,5"` EventId *string } +var filtersMapping = map[string]string{ + StackEventFilterLogicalID: "LogicalResourceId", + StackEventFilterTimestamp: "Timestamp", + StackEventFilterStatus: "ResourceStatus", + StackEventFilterStatusReason: "ResourceStatusReason", + StackEventFilterType: "ResourceType", + StackEventFilterPhysicalId: "PhysicalResourceId", +} + +var DefaultStackEventFilters = []string{StackEventFilterTimestamp, StackEventFilterLogicalID, StackEventFilterType, StackEventFilterStatus} +var AllStackEventFilters = append(DefaultStackEventFilters, StackEventFilterStatusReason, StackEventFilterPhysicalId) + type stackEvents []stackEvent func NewCloudformationEventsTailer(stackName string, nbEvents int, enableFollow bool, frequency time.Duration, f filters, timeout time.Duration, cancelAfterTimeout bool) *stackEventTailer { @@ -127,7 +140,7 @@ func (t *stackEventTailer) Tail(w io.Writer) error { if t.deploymentStatus.isFinished { if len(t.deploymentStatus.failedEvents) > 0 { var errBuf bytes.Buffer - var f filters = []string{StackEventLogicalID, StackEventType, StackEventStatus, StackEventStatusReason} + var f filters = []string{StackEventFilterLogicalID, StackEventFilterType, StackEventFilterStatus, StackEventFilterStatusReason} if isTimeoutReached { errBuf.WriteString("Update was cancelled because timeout has been reached and option 'Cancel On Timeout' enabled\n") @@ -137,7 +150,8 @@ func (t *stackEventTailer) Tail(w io.Writer) error { errBuf.WriteString("Failed events summary:\n") - // printing error events as a nice table + // using tabwriter here, because we have all data + // and no need to stream it errTab := tabwriter.NewWriter(&errBuf, 25, 8, 0, '\t', 0) errTab.Write(f.header()) t.deploymentStatus.failedEvents.printReverse(errTab, f) @@ -310,12 +324,12 @@ func (e stackEvents) printReverse(w io.Writer, f filters) error { func (f filters) header() []byte { s := &stackEvent{ - Timestamp: func() *string { t := "Timestamp"; return &t }(), - ResourceStatus: func() *string { t := "Status"; return &t }(), - LogicalResourceId: func() *string { t := "Logical ID"; return &t }(), - PhysicalResourceId: func() *string { t := "Physical ID"; return &t }(), - ResourceStatusReason: func() *string { t := "Status Reason"; return &t }(), - ResourceType: func() *string { t := "Type"; return &t }(), + Timestamp: func() *string { t := color.New(color.Bold).Sprintf("Timestamp"); return &t }(), + ResourceStatus: func() *string { t := color.New(color.Bold).Sprintf("Status"); return &t }(), + LogicalResourceId: func() *string { t := color.New(color.Bold).Sprintf("Logical ID"); return &t }(), + PhysicalResourceId: func() *string { t := color.New(color.Bold).Sprintf("Physical ID"); return &t }(), + ResourceStatusReason: func() *string { t := color.New(color.Bold).Sprintf("Status Reason"); return &t }(), + ResourceType: func() *string { t := color.New(color.Bold).Sprintf("Type"); return &t }(), } return s.format(f) @@ -353,7 +367,7 @@ func (s *stackEventTailer) cancelStackUpdate(cfn *awsservices.Cloudformation) er func NewStackEvent(e *cloudformation.StackEvent) stackEvent { return stackEvent{ Timestamp: func() *string { t := e.Timestamp.Format(time.RFC3339); return &t }(), - ResourceStatus: e.ResourceStatus, + ResourceStatus: colorizeResourceStatus(*e.ResourceStatus), ResourceType: e.ResourceType, LogicalResourceId: e.LogicalResourceId, PhysicalResourceId: e.PhysicalResourceId, @@ -362,68 +376,64 @@ func NewStackEvent(e *cloudformation.StackEvent) stackEvent { } } -// Format dynamically generates fixed position for each field -// by adding struct tag like `fixed:"start,end"` for -// further marshaling into structured field by package -// "github.com/ianlopshire/go-fixedwidth" +// Format reads the struct tag `width:","` +// further marshaling into structured field func (s *stackEvent) format(fil filters) []byte { - st := reflect.TypeOf(s).Elem() - sv := reflect.ValueOf(s).Elem() - var startPos = 1 - var fs []reflect.StructField - - // copying original struct fields - for i := 0; i < st.NumField(); i++ { - fs = append(fs, st.Field(i)) - } + tp := reflect.TypeOf(s).Elem() + v := reflect.ValueOf(s).Elem() + + buf := bytes.Buffer{} + for _, f := range fil { + field, ok := tp.FieldByName(filtersMapping[f]) + if !ok { + continue + } + value := v.FieldByName(filtersMapping[f]) - // applying tags to the struct fields based on - // provided filters values - for _, fil := range fil { - for i := 0; i < len(fs); i++ { - tag := s.getFieldPosition(fs[i].Name, &startPos, fil) - if tag == nil { - continue - } - fs[i].Tag = reflect.StructTag(*tag) + split := strings.Split(field.Tag.Get("width"), ",") + if len(split) != 2 { + continue } - } - // creating new structure based on it's fields - st2 := reflect.StructOf(fs) - // copying values from original structure to the new one - sv2 := sv.Convert(st2) - - // marshalling struct to the string of fixed size - // based on applyed tags - b, _ := fixedwidth.Marshal(sv2.Interface()) - return append(b, []byte("\n")...) -} -func (s *stackEvent) getFieldPosition(field string, startPos *int, f string) *string { - const space = 5 - var endPos = *startPos + width, err := strconv.Atoi(split[0]) + if err != nil { + continue + } - switch { - case f == StackEventLogicalID && s.LogicalResourceId != nil && field == "LogicalResourceId": - endPos = *startPos + 20 + space - case f == StackEventTimestamp && s.Timestamp != nil && field == "Timestamp": - endPos = *startPos + 18 + space - case f == StackEventStatus && s.ResourceStatus != nil && field == "ResourceStatus": - s.ResourceStatus = colorizeResourceStatus(*s.ResourceStatus) - // 53 - symbols, longest CF resource name: "AWS::KinesisAnalytics::ApplicationReferenceDataSource" - endPos = *startPos + 53 + space - case f == StackEventStatusReason && s.ResourceStatusReason != nil && field == "ResourceStatusReason": - // resource status reason, copping on 60 characters. complete error will - // be displayed as summary when the tailing complete - endPos = *startPos + 60 + space - case f == StackEventType && s.ResourceType != nil && field == "ResourceType": - // 45 - longest CF status "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS" - endPos = *startPos + 45 + space - default: - return nil + space, err := strconv.Atoi(split[1]) + if err != nil { + continue + } + + var v string + if !value.IsNil() { + v = value.Elem().String() + } + + // handle coloring + // if string starts with "\x1b" then it is colored + if strings.HasPrefix(v, "\x1b") { + // color adds additional length to the string + // which is not displayed in the console + // and results in text shift + // so we need to increase column width a bit + // colored string looks like: "\x1b[31mText\x1b[0m" + width += strings.Index(v, "m") + 1 + len("\x1b[0m") + } + + // crop string if it longer then defined width + if len(v) > width { + v = v[:width] + } + + buf.WriteString(v) + + // fil the rest of the line space with " " + for i := len(v); i < width+space; i++ { + buf.WriteString(" ") + } } - tag := fmt.Sprintf(`fixed:"%d,%d"`, *startPos, endPos) - *startPos = endPos + 1 - return &tag + buf.WriteRune('\n') + return buf.Bytes() } diff --git a/commands/tail.go b/commands/tail.go index 0abdfac32..a75517740 100644 --- a/commands/tail.go +++ b/commands/tail.go @@ -19,6 +19,7 @@ package commands import ( "fmt" "os" + "strings" "time" "github.com/spf13/cobra" @@ -42,13 +43,8 @@ func init() { tailCmd.AddCommand(scalingActivitiesCmd) stackEventsCmd.PersistentFlags().StringArrayVar(&stackEventsFilters, "filters", - []string{awstailers.StackEventTimestamp, awstailers.StackEventLogicalID, awstailers.StackEventType, awstailers.StackEventStatus}, - fmt.Sprintf("Filter the output columns. Valid filters: %s, %s, %s, %s, %s", - awstailers.StackEventLogicalID, - awstailers.StackEventStatus, - awstailers.StackEventStatusReason, - awstailers.StackEventTimestamp, - awstailers.StackEventType)) + awstailers.DefaultStackEventFilters, + fmt.Sprintf("Filter the output columns. Valid filters: %s", strings.Join(awstailers.AllStackEventFilters, ","))) stackEventsCmd.PersistentFlags().BoolVar(&cancelStackUpdateAfterTimeout, "cancel-on-timeout", false, "Cancel stack update when timeout is reached, use with 'timeout' flag") stackEventsCmd.PersistentFlags().DurationVar(&stackEventsTailTimeout, "timeout", time.Duration(1*time.Hour), "Time to wait for stack update to complete, use with 'follow' flag") From ea546b33da31fff2455908583c3e7d59e4b0c3b6 Mon Sep 17 00:00:00 2001 From: Taras Postument Date: Sat, 31 Mar 2018 00:16:12 +0300 Subject: [PATCH 4/9] remove not needed test and dependency --- Gopkg.lock | 8 +- Gopkg.toml | 5 +- aws/tailers/stack_event_test.go | 28 -- .../ianlopshire/go-fixedwidth/.gitignore | 17 -- .../ianlopshire/go-fixedwidth/LICENSE | 21 -- .../ianlopshire/go-fixedwidth/README.md | 67 ----- .../ianlopshire/go-fixedwidth/decode.go | 251 ------------------ .../ianlopshire/go-fixedwidth/decode_test.go | 185 ------------- .../ianlopshire/go-fixedwidth/encode.go | 228 ---------------- .../ianlopshire/go-fixedwidth/encode_test.go | 128 --------- .../ianlopshire/go-fixedwidth/fixedwidth.go | 2 - .../go-fixedwidth/fixedwidth_test.go | 31 --- .../ianlopshire/go-fixedwidth/tags.go | 28 -- .../ianlopshire/go-fixedwidth/tags_test.go | 44 --- 14 files changed, 2 insertions(+), 1041 deletions(-) delete mode 100644 aws/tailers/stack_event_test.go delete mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/.gitignore delete mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/LICENSE delete mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/README.md delete mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/decode.go delete mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/decode_test.go delete mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/encode.go delete mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/encode_test.go delete mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth.go delete mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth_test.go delete mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/tags.go delete mode 100644 vendor/github.com/ianlopshire/go-fixedwidth/tags_test.go diff --git a/Gopkg.lock b/Gopkg.lock index d84c77233..85cefe67c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -119,12 +119,6 @@ revision = "7f08801859139f86dfafd1c296e2cba9a80d292e" version = "v1.6.0" -[[projects]] - name = "github.com/ianlopshire/go-fixedwidth" - packages = ["."] - revision = "b5f5b05297fca5895c0c6d9b4c8dda091b84cfd6" - version = "v0.2.1" - [[projects]] name = "github.com/inconshreveable/mousetrap" packages = ["."] @@ -331,6 +325,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "dc5a8334da384b8de743df843e031a98ecec1fcdcb02be3e7a9819584032bc1c" + inputs-digest = "c16569f26e1430b337ef16bbc9e19239de261ca85cc54b76ff67116e4eacabc7" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 2e00ad52b..f1c55a485 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -75,7 +75,4 @@ [[constraint]] branch = "master" - name = "github.com/boombuler/barcode" -[[constraint]] - name = "github.com/ianlopshire/go-fixedwidth" - version = "0.2.1" + name = "github.com/boombuler/barcode" \ No newline at end of file diff --git a/aws/tailers/stack_event_test.go b/aws/tailers/stack_event_test.go deleted file mode 100644 index c7dc6f3e7..000000000 --- a/aws/tailers/stack_event_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package awstailers - -import ( - "reflect" - "testing" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/cloudformation" -) - -type stackEventTest struct { - *cloudformation.StackEvent -} - -func TestReflect(t *testing.T) { - a := stackEventTest{ - &cloudformation.StackEvent{ - EventId: aws.String("sdasda"), - ResourceStatus: aws.String("dsada"), - }, - } - v, _ := reflect.TypeOf(a).Elem().FieldByName("EventId") - - v.Tag = reflect.StructTag(`fixed:"12,22"`) - - t.Log(v.Tag.Get("fixed")) - // t.Log(v.FieldByName("type")) -} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/.gitignore b/vendor/github.com/ianlopshire/go-fixedwidth/.gitignore deleted file mode 100644 index e4e8855cf..000000000 --- a/vendor/github.com/ianlopshire/go-fixedwidth/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.dll -*.so -*.dylib - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 -.glide/ - -# JetBrains IDE related files -.idea/ \ No newline at end of file diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/LICENSE b/vendor/github.com/ianlopshire/go-fixedwidth/LICENSE deleted file mode 100644 index 7fa2906e3..000000000 --- a/vendor/github.com/ianlopshire/go-fixedwidth/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 Ian Lopshire - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/README.md b/vendor/github.com/ianlopshire/go-fixedwidth/README.md deleted file mode 100644 index 246d22e0a..000000000 --- a/vendor/github.com/ianlopshire/go-fixedwidth/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# fixedwidth [![GoDoc](https://godoc.org/github.com/ianlopshire/go-fixedwidth?status.svg)](http://godoc.org/github.com/ianlopshire/go-fixedwidth) [![Report card](https://goreportcard.com/badge/github.com/ianlopshire/go-fixedwidth)](https://goreportcard.com/report/github.com/ianlopshire/go-fixedwidth) [![Go Cover](http://gocover.io/_badge/github.com/ianlopshire/go-fixedwidth)](http://gocover.io/github.com/ianlopshire/go-fixedwidth) - -Package fixedwidth provides encoding and decoding for fixed-width formatted Data. - -`go get github.com/ianlopshire/go-fixedwidth` - -## Usage - -### Struct Tags -Position within a line is controlled via struct tags. -The tags should be formatted as `fixed:"{startPos},{endPos}"` where `startPos` and `endPos` are both positive integers greater than 0. -Positions start at 1. The interval is inclusive. Fields without tags are ignored. - -### Encode -```go -// define some data to encode -people := []struct { - ID int `fixed:"1,5"` - FirstName string `fixed:"6,15"` - LastName string `fixed:"16,25"` - Grade float64 `fixed:"26,30"` -}{ - {1, "Ian", "Lopshire", 99.5}, -} - -data, err := fixedwidth.Marshal(people) -if err != nil { - log.Fatal(err) -} -fmt.Printf("%s", data) -// Output: -// 1 Ian Lopshire 99.50 -``` - -### Decode -```go -// define the format -var people []struct { - ID int `fixed:"1,5"` - FirstName string `fixed:"6,15"` - LastName string `fixed:"16,25"` - Grade float64 `fixed:"26,30"` -} - -// define some fixed-with data to parse -data := []byte("" + - "1 Ian Lopshire 99.50" + "\n" + - "2 John Doe 89.50" + "\n" + - "3 Jane Doe 79.50" + "\n") - - -err := fixedwidth.Unmarshal(data, &people) -if err != nil { - log.Fatal(err) -} - -fmt.Printf("%+v\n", people[0]) -fmt.Printf("%+v\n", people[1]) -fmt.Printf("%+v\n", people[2]) -// Output: -//{ID:1 FirstName:Ian LastName:Lopshire Grade:99.5} -//{ID:2 FirstName:John LastName:Doe Grade:89.5} -//{ID:3 FirstName:Jane LastName:Doe Grade:79.5} -``` - -## Licence -MIT diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/decode.go b/vendor/github.com/ianlopshire/go-fixedwidth/decode.go deleted file mode 100644 index 77e82bb86..000000000 --- a/vendor/github.com/ianlopshire/go-fixedwidth/decode.go +++ /dev/null @@ -1,251 +0,0 @@ -package fixedwidth - -import ( - "bufio" - "bytes" - "encoding" - "errors" - "io" - "reflect" - "strconv" -) - -// Unmarshal parses fixed width encoded data and stores the -// result in the value pointed to by v. If v is nil or not a -// pointer, Unmarshal returns an InvalidUnmarshalError. -func Unmarshal(data []byte, v interface{}) error { - return NewDecoder(bytes.NewReader(data)).Decode(v) -} - -// A Decoder reads and decodes fixed width data from an input stream. -type Decoder struct { - data *bufio.Reader - done bool -} - -// NewDecoder returns a new decoder that reads from r. -func NewDecoder(r io.Reader) *Decoder { - return &Decoder{ - data: bufio.NewReader(r), - } -} - -// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. -// (The argument to Unmarshal must be a non-nil pointer.) -type InvalidUnmarshalError struct { - Type reflect.Type -} - -func (e *InvalidUnmarshalError) Error() string { - if e.Type == nil { - return "fixedwidth: Unmarshal(nil)" - } - - if e.Type.Kind() != reflect.Ptr { - return "fixedwidth: Unmarshal(non-pointer " + e.Type.String() + ")" - } - return "fixedwidth: Unmarshal(nil " + e.Type.String() + ")" -} - -// An UnmarshalTypeError describes a value that was -// not appropriate for a value of a specific Go type. -type UnmarshalTypeError struct { - Value string // the raw value - Type reflect.Type // type of Go value it could not be assigned to - Struct string // name of the struct type containing the field - Field string // name of the field holding the Go value - Cause error // original error -} - -func (e *UnmarshalTypeError) Error() string { - var s string - if e.Struct != "" || e.Field != "" { - s = "fixedwidth: cannot unmarshal " + e.Value + " into Go struct field " + e.Struct + "." + e.Field + " of type " + e.Type.String() - } else { - s = "fixedwidth: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() - } - if e.Cause != nil { - return s + ":" + e.Cause.Error() - } - return s -} - -// Decode reads from its input and stores the decoded data to the value -// pointed to by v. -// -// In the case that v points to a struct value, Decode will read a -// single line from the input. -// -// In the case that v points to a slice value, Decode will read until -// the end of its input. -func (d *Decoder) Decode(v interface{}) error { - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr || rv.IsNil() { - return &InvalidUnmarshalError{reflect.TypeOf(v)} - } - - if reflect.Indirect(reflect.ValueOf(v)).Kind() == reflect.Slice { - return d.readLines(reflect.ValueOf(v).Elem()) - } - return d.readLine(reflect.ValueOf(v)) -} - -func (d *Decoder) readLines(v reflect.Value) (err error) { - ct := v.Type().Elem() - for { - nv := reflect.New(ct).Elem() - err := d.readLine(nv) - if err != nil { - return err - } - if d.done { - break - } - v.Set(reflect.Append(v, nv)) - } - return nil -} - -func (d *Decoder) readLine(v reflect.Value) (err error) { - // TODO: properly handle prefixed lines - line, _, err := d.data.ReadLine() - if err == io.EOF { - d.done = true - return nil - } else if err != nil { - return err - } - - return newValueSetter(v.Type())(v, line) -} - -func rawValueFromLine(line []byte, startPos, endPos int) []byte { - if len(line) == 0 || startPos >= len(line) { - return []byte{} - } - if endPos > len(line) { - endPos = len(line) - } - return bytes.TrimSpace(line[startPos-1:endPos]) -} - -type valueSetter func(v reflect.Value, raw []byte) error - -var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() - -func newValueSetter(t reflect.Type) valueSetter { - if t.Implements(textUnmarshalerType) { - return textUnmarshalerSetter(t, false) - } - if reflect.PtrTo(t).Implements(textUnmarshalerType) { - return textUnmarshalerSetter(t, true) - } - - switch t.Kind() { - case reflect.Ptr: - return ptrSetter(t) - case reflect.Interface: - return interfaceSetter - case reflect.Struct: - return structSetter - case reflect.String: - return stringSetter - case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: - return intSetter - case reflect.Float32: - return floatSetter(32) - case reflect.Float64: - return floatSetter(64) - } - return unknownSetter -} - -func structSetter (v reflect.Value, raw []byte) error { - t := v.Type() - for i := 0; i < v.NumField(); i++ { - fv := v.Field(i) - if !fv.IsValid() { - continue - } - sf := t.Field(i) - startPos, endPos, ok := parseTag(sf.Tag.Get("fixed")) - if !ok { - continue - } - rawValue := rawValueFromLine(raw, startPos, endPos) - err := newValueSetter(sf.Type)(fv, rawValue) - if err != nil { - return &UnmarshalTypeError{string(rawValue), sf.Type, t.Name(), sf.Name, err} - } - } - return nil -} - -func unknownSetter(v reflect.Value, raw []byte) error { - return errors.New("fixedwidth: unknown type") -} - -func nilSetter(v reflect.Value, _ []byte) error { - v.Set(reflect.Zero(v.Type())) - return nil -} - -func textUnmarshalerSetter(t reflect.Type, shouldAddr bool) valueSetter { - return func(v reflect.Value, raw []byte) error { - if shouldAddr { - v = v.Addr() - } - // set to zero value if this is nil - if t.Kind() == reflect.Ptr && v.IsNil(){ - v.Set(reflect.New(t.Elem())) - } - return v.Interface().(encoding.TextUnmarshaler).UnmarshalText(raw) - } -} - -func interfaceSetter(v reflect.Value, raw []byte) error { - return newValueSetter(v.Elem().Type())(v.Elem(), raw) -} - -func ptrSetter(t reflect.Type) valueSetter { - return func(v reflect.Value, raw []byte) error { - if len(raw) <= 0 { - return nilSetter(v, raw) - } - if v.IsNil() { - v.Set(reflect.New(t.Elem())) - } - return newValueSetter(v.Elem().Type())(reflect.Indirect(v), raw) - } -} - -func stringSetter(v reflect.Value, raw []byte) error { - v.SetString(string(raw)) - return nil -} - -func intSetter(v reflect.Value, raw []byte) error { - if len(raw) < 1 { - return nil - } - i, err := strconv.Atoi(string(raw)) - if err != nil { - return err - } - v.SetInt(int64(i)) - return nil -} - -func floatSetter(bitSize int) valueSetter { - return func(v reflect.Value, raw []byte) error { - if len(raw) < 1 { - return nil - } - f, err := strconv.ParseFloat(string(raw), bitSize) - if err != nil { - return err - } - v.SetFloat(f) - return nil - } -} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/decode_test.go b/vendor/github.com/ianlopshire/go-fixedwidth/decode_test.go deleted file mode 100644 index 645a297f0..000000000 --- a/vendor/github.com/ianlopshire/go-fixedwidth/decode_test.go +++ /dev/null @@ -1,185 +0,0 @@ -package fixedwidth - -import ( - "fmt" - "log" - "reflect" - "testing" - "encoding" -) - -func ExampleUnmarshal() { - // define the format - var people []struct { - ID int `fixed:"1,5"` - FirstName string `fixed:"6,15"` - LastName string `fixed:"16,25"` - Grade float64 `fixed:"26,30"` - } - - // define some fixed-with data to parse - data := []byte("" + - "1 Ian Lopshire 99.50" + "\n" + - "2 John Doe 89.50" + "\n" + - "3 Jane Doe 79.50" + "\n") - - err := Unmarshal(data, &people) - if err != nil { - log.Fatal(err) - } - - fmt.Printf("%+v\n", people[0]) - fmt.Printf("%+v\n", people[1]) - fmt.Printf("%+v\n", people[2]) - // Output: - //{ID:1 FirstName:Ian LastName:Lopshire Grade:99.5} - //{ID:2 FirstName:John LastName:Doe Grade:89.5} - //{ID:3 FirstName:Jane LastName:Doe Grade:79.5} -} - -func TestUnmarshal(t *testing.T) { - // allTypes contains a field with all current supported types. - type allTypes struct { - String string `fixed:"1,5"` - Int int `fixed:"6,10"` - Float float64 `fixed:"11,15"` - TextUnmarshaler EncodableString `fixed:"16,20"` - } - for _, tt := range []struct { - name string - rawValue []byte - target interface{} - expected interface{} - shouldErr bool - }{ - { - name: "Basic Slice Case", - rawValue: []byte("foo 123 1.2 bar" + "\n" + "bar 321 2.1 foo"), - target: &[]allTypes{}, - expected: &[]allTypes{ - {"foo", 123, 1.2, EncodableString{"bar", nil}}, - {"bar", 321, 2.1, EncodableString{"foo", nil}}, - }, - shouldErr: false, - }, - { - name: "Basic Struct Case", - rawValue: []byte("foo 123 1.2 bar"), - target: &allTypes{}, - expected: &allTypes{"foo", 123, 1.2, EncodableString{"bar", nil}}, - shouldErr: false, - }, - { - name: "Unmarshal Error", - rawValue: []byte("foo nan ddd bar"), - target: &allTypes{}, - expected: &allTypes{}, - shouldErr: true, - }, - { - name: "Empty Line", - rawValue: []byte(""), - target: &allTypes{}, - expected: &allTypes{"", 0, 0, EncodableString{"", nil}}, - shouldErr: false, - }, - { - name: "Invalid Target", - rawValue: []byte("foo 123 1.2 bar"), - target: allTypes{}, - expected: allTypes{}, - shouldErr: true, - }, - } { - t.Run(tt.name, func(t *testing.T) { - err := Unmarshal(tt.rawValue, tt.target) - if tt.shouldErr != (err != nil) { - t.Errorf("Unmarshal() err want %v, have %v (%v)", tt.shouldErr, err != nil, err.Error()) - } - if !tt.shouldErr && !reflect.DeepEqual(tt.target, tt.expected) { - t.Errorf("Unmarshal() want %+v, have %+v", tt.expected, tt.target) - } - - }) - } - - t.Run("Invalid Unmarshal Errors", func(t *testing.T) { - for _, tt := range []struct { - name string - v interface{} - shouldErr bool - }{ - {"Invalid Unmarshal Nil", nil, true}, - {"Invalid Unmarshal Not Pointer 1", struct{}{}, true}, - {"Invalid Unmarshal Not Pointer 2", []struct{}{}, true}, - {"Valid Unmarshal slice", &[]struct{}{}, false}, - {"Valid Unmarshal struct", &struct{}{}, false}, - } { - t.Run(tt.name, func(t *testing.T) { - err := Unmarshal([]byte{}, tt.v) - if tt.shouldErr != (err != nil) { - t.Errorf("Unmarshal() err want %v, have %v (%v)", tt.shouldErr, err != nil, err) - } - }) - } - }) -} - -func TestNewValueSetter(t *testing.T) { - for _, tt := range []struct { - name string - raw []byte - expected interface{} - shouldErr bool - }{ - {"invalid type", []byte("foo"), true, true}, - - {"textUnmarshaler implementation", []byte("foo"), &EncodableString{"foo", nil}, false}, - {"textUnmarshaler implementation if addressed", []byte("foo"), EncodableString{"foo", nil}, false}, - {"textUnmarshaler implementation as interface", []byte("foo"), encoding.TextUnmarshaler(&EncodableString{"foo", nil}), false}, - {"textUnmarshaler implementation in interface", []byte("foo"), interface{}(&EncodableString{"foo", nil}), false}, - {"textUnmarshaler implementation if addressed in interface", []byte("foo"), interface{}(EncodableString{"foo", nil}), false}, - - {"string", []byte("foo"), string("foo"), false}, - {"string empty", []byte(""), string(""), false}, - {"string interface", []byte("foo"), interface{}(string("foo")), false}, - {"string interface empty", []byte(""), interface{}(string("")), false}, - {"*string", []byte("foo"), stringp("foo"), false}, - {"*string empty", []byte(""), (*string)(nil), false}, - - {"int", []byte("1"), int(1), false}, - {"int zero", []byte("0"), int(0), false}, - {"int empty", []byte(""), int(0), false}, - {"*int", []byte("1"), intp(1), false}, - {"*int zero", []byte("0"), intp(0), false}, - {"*int empty", []byte(""), (*int)(nil), false}, - {"int Invalid", []byte("foo"), int(0), true}, - - {"float64", []byte("1.23"), float64(1.23), false}, - {"*float64", []byte("1.23"), float64p(1.23), false}, - {"*float64 zero", []byte("0"), float64p(0), false}, - {"*float64 empty", []byte(""), (*float64)(nil), false}, - {"float64 Invalid", []byte("foo"), float64(0), true}, - - {"float32", []byte("1.23"), float32(1.23), false}, - {"float32 Invalid", []byte("foo"), float32(0), true}, - - {"int8", []byte("1"), int8(1), false}, - {"int16", []byte("1"), int16(1), false}, - {"int32", []byte("1"), int32(1), false}, - {"int64", []byte("1"), int64(1), false}, - } { - t.Run(tt.name, func(t *testing.T) { - // ensure we have an addressable target - var i = reflect.Indirect(reflect.New(reflect.TypeOf(tt.expected))) - - err := newValueSetter(i.Type())(i, tt.raw) - if tt.shouldErr != (err != nil) { - t.Errorf("newValueSetter(%s)() err want %v, have %v (%v)", reflect.TypeOf(tt.expected).Name(), tt.shouldErr, err != nil, err.Error()) - } - if !tt.shouldErr && !reflect.DeepEqual(tt.expected, i.Interface()) { - t.Errorf("newValueSetter(%s)() want %s, have %s", reflect.TypeOf(tt.expected).Name(), tt.expected, i) - } - }) - } -} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/encode.go b/vendor/github.com/ianlopshire/go-fixedwidth/encode.go deleted file mode 100644 index 51c5907d5..000000000 --- a/vendor/github.com/ianlopshire/go-fixedwidth/encode.go +++ /dev/null @@ -1,228 +0,0 @@ -package fixedwidth - -import ( - "bufio" - "bytes" - "encoding" - "io" - "reflect" - "strconv" -) - -// Marshal returns the fixed-width encoding of v. -// -// v must be an encodable type or a slice of an encodable -// type. If v is a slice, each item will be treated as a -// line. If v is a single encodable type, a single line -// will be encoded. -// -// In order for a type to be encodable, it must implement -// the encoding.TextMarshaler interface or be based on one -// of the following builtin types: string, int, int64, -// int32, int16, int8, float64, float32, or struct. -// Pointers to encodable types and interfaces containing -// encodable types are also encodable. -// -// nil pointers and interfaces will be omitted. zero vales -// will be encoded normally. -// -// A struct is encoded to a single slice of bytes. Each -// field in a struct will be encoded and placed at the -// position defined by its struct tags. The tags should be -// formatted as `fixed:"{startPos},{endPos}"`. Positions -// start at 1. The interval is inclusive. Fields without -// tags and Fields of an un-encodable type are ignored. -// -// If the encoded value of a field is longer than the -// length of the position interval, the overflow is -// truncated. -func Marshal(v interface{}) ([]byte, error) { - buff := bytes.NewBuffer(nil) - err := NewEncoder(buff).Encode(v) - if err != nil { - return nil, err - } - return buff.Bytes(), nil -} - -// MarshalInvalidTypeError describes an invalid type being marshaled. -type MarshalInvalidTypeError struct { - typeName string -} - -func (e *MarshalInvalidTypeError) Error() string { - return "fixedwidth: cannot marshal unknown Type " + e.typeName -} - -// An Encoder writes fixed-width formatted data to an output -// stream. -type Encoder struct { - w *bufio.Writer -} - -// NewEncoder returns a new encoder that writes to w. -func NewEncoder(w io.Writer) *Encoder { - return &Encoder{ - bufio.NewWriter(w), - } -} - -// Encode writes the fixed-width encoding of v to the -// stream. -// See the documentation for Marshal for details about -// encoding behavior. -func (e *Encoder) Encode(i interface{}) (err error) { - if i == nil { - return nil - } - - // check to see if i should be encoded into multiple lines - v := reflect.ValueOf(i) - for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { - v = v.Elem() - } - if v.Kind() == reflect.Slice { - // encode each slice element to a line - err = e.writeLines(v) - } else { - // this is a single object so encode the original vale to a line - err = e.writeLine(reflect.ValueOf(i)) - } - if err != nil { - return err - } - return e.w.Flush() -} - -func (e *Encoder) writeLines(v reflect.Value) error { - for i := 0; i < v.Len(); i++ { - err := e.writeLine(v.Index(i)) - if err != nil { - return err - } - - if i != v.Len()-1 { - _, err := e.w.Write([]byte("\n")) - if err != nil { - return err - } - } - } - return nil -} - -func (e *Encoder) writeLine(v reflect.Value) (err error) { - b, err := newValueEncoder(v.Type())(v) - if err != nil { - return err - } - _, err = e.w.Write(b) - return err -} - -type valueEncoder func(v reflect.Value) ([]byte, error) - -func newValueEncoder(t reflect.Type) valueEncoder { - if t == nil { - return nilEncoder - } - if t.Implements(reflect.TypeOf(new(encoding.TextMarshaler)).Elem()) { - return textMarshalerEncoder - } - - switch t.Kind() { - case reflect.Ptr, reflect.Interface: - return ptrInterfaceEncoder - case reflect.Struct: - return structEncoder - case reflect.String: - return stringEncoder - case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: - return intEncoder - case reflect.Float64: - return floatEncoder(2, 64) - case reflect.Float32: - return floatEncoder(2, 32) - } - return unknownTypeEncoder(t) -} - -func structEncoder(v reflect.Value) ([]byte, error) { - var specs []fieldSpec - for i := 0; i < v.Type().NumField(); i++ { - f := v.Type().Field(i) - var ( - err error - spec fieldSpec - ok bool - ) - spec.startPos, spec.endPos, ok = parseTag(f.Tag.Get("fixed")) - if !ok { - continue - } - spec.value, err = newValueEncoder(f.Type)(v.Field(i)) - if err != nil { - return nil, err - } - specs = append(specs, spec) - } - return encodeSpecs(specs), nil -} - -type fieldSpec struct { - startPos, endPos int - value []byte -} - -func encodeSpecs(specs []fieldSpec) []byte { - var ll int - for _, spec := range specs { - if spec.endPos > ll { - ll = spec.endPos - } - } - data := bytes.Repeat([]byte(" "), ll) - for _, spec := range specs { - for i, b := range spec.value { - if spec.startPos+i <= spec.endPos { - data[spec.startPos+i-1] = b - } - } - } - return data -} - -func textMarshalerEncoder(v reflect.Value) ([]byte, error) { - return v.Interface().(encoding.TextMarshaler).MarshalText() -} - -func ptrInterfaceEncoder(v reflect.Value) ([]byte, error) { - if v.IsNil() { - return nilEncoder(v) - } - return newValueEncoder(v.Elem().Type())(v.Elem()) -} - -func stringEncoder(v reflect.Value) ([]byte, error) { - return []byte(v.String()), nil -} - -func intEncoder(v reflect.Value) ([]byte, error) { - return []byte(strconv.Itoa(int(v.Int()))), nil -} - -func floatEncoder(perc, bitSize int) valueEncoder { - return func(v reflect.Value) ([]byte, error) { - return []byte(strconv.FormatFloat(v.Float(), 'f', perc, bitSize)), nil - } -} - -func nilEncoder(v reflect.Value) ([]byte, error) { - return nil, nil -} - -func unknownTypeEncoder(t reflect.Type) valueEncoder { - return func(value reflect.Value) ([]byte, error) { - return nil, &MarshalInvalidTypeError{typeName: t.Name()} - } -} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/encode_test.go b/vendor/github.com/ianlopshire/go-fixedwidth/encode_test.go deleted file mode 100644 index acf50d07f..000000000 --- a/vendor/github.com/ianlopshire/go-fixedwidth/encode_test.go +++ /dev/null @@ -1,128 +0,0 @@ -package fixedwidth - -import ( - "bytes" - "fmt" - "github.com/pkg/errors" - "log" - "reflect" - "testing" -) - -func ExampleMarshal() { - // define some data to encode - people := []struct { - ID int `fixed:"1,5"` - FirstName string `fixed:"6,15"` - LastName string `fixed:"16,25"` - Grade float64 `fixed:"26,30"` - }{ - {1, "Ian", "Lopshire", 99.5}, - } - - data, err := Marshal(people) - if err != nil { - log.Fatal(err) - } - fmt.Printf("%s", data) - // Output: - // 1 Ian Lopshire 99.50 -} - -func TestMarshal(t *testing.T) { - type H struct { - F1 interface{} `fixed:"1,5"` - F2 interface{} `fixed:"6,10"` - } - tagHelper := struct { - Valid string `fixed:"1,5"` - NoTags string - InvalidTags string `fixed:"5"` - }{"foo", "foo", "foo"} - marshalError := errors.New("marshal error") - - for _, tt := range []struct { - name string - i interface{} - o []byte - shouldErr bool - }{ - {"single line", H{"foo", 1}, []byte("foo 1 "), false}, - {"multiple line", []H{{"foo", 1}, {"bar", 2}}, []byte("foo 1 \nbar 2 "), false}, - {"empty slice", []H{}, nil, false}, - {"pointer", &H{"foo", 1}, []byte("foo 1 "), false}, - {"nil", nil, nil, false}, - {"invalid type", true, nil, true}, - {"invalid type in struct", H{"foo", true}, nil, true}, - {"marshal error", EncodableString{"", marshalError}, nil, true}, - {"invalid tags", tagHelper, []byte("foo "), false}, - } { - t.Run(tt.name, func(t *testing.T) { - o, err := Marshal(tt.i) - if tt.shouldErr != (err != nil) { - t.Errorf("Marshal() shouldErr expected %v, have %v (%v)", tt.shouldErr, err != nil, err) - } - if !tt.shouldErr && !bytes.Equal(o, tt.o) { - t.Errorf("Marshal() expected %s, have %s", tt.o, o) - } - }) - } -} - -func TestNewValueEncoder(t *testing.T) { - for _, tt := range []struct { - name string - i interface{} - o []byte - shouldErr bool - }{ - {"nil", nil, []byte(""), false}, - {"nil interface", interface{}(nil), []byte(""), false}, - - {"[]string (invalid)", []string{"a", "b"}, []byte(""), true}, - {"[]string interface (invalid)", interface{}([]string{"a", "b"}), []byte(""), true}, - {"bool (invalid)", true, []byte(""), true}, - - {"string", "foo", []byte("foo"), false}, - {"string interface", interface{}("foo"), []byte("foo"), false}, - {"string empty", "", []byte(""), false}, - {"*string", stringp("foo"), []byte("foo"), false}, - {"*string empty", stringp(""), []byte(""), false}, - {"*string nil", nilString, []byte(""), false}, - - {"float64", float64(123.4567), []byte("123.46"), false}, - {"float64 interface", interface{}(float64(123.4567)), []byte("123.46"), false}, - {"float64 zero", float64(0), []byte("0.00"), false}, - {"*float64", float64p(123.4567), []byte("123.46"), false}, - {"*float64 zero", float64p(0), []byte("0.00"), false}, - {"*float64 nil", nilFloat64, []byte(""), false}, - - {"float32", float32(123.4567), []byte("123.46"), false}, - {"float32 interface", interface{}(float32(123.4567)), []byte("123.46"), false}, - {"float32 zero", float32(0), []byte("0.00"), false}, - {"*float32", float32p(123.4567), []byte("123.46"), false}, - {"*float32 zero", float32p(0), []byte("0.00"), false}, - {"*float32 nil", nilFloat32, []byte(""), false}, - - {"int", int(123), []byte("123"), false}, - {"int interface", interface{}(int(123)), []byte("123"), false}, - {"int zero", int(0), []byte("0"), false}, - {"*int", intp(123), []byte("123"), false}, - {"*int zero", intp(0), []byte("0"), false}, - {"*int nil", nilInt, []byte(""), false}, - - {"TextUnmarshaler", EncodableString{"foo", nil}, []byte("foo"), false}, - {"TextUnmarshaler interface", interface{}(EncodableString{"foo", nil}), []byte("foo"), false}, - {"TextUnmarshaler error", EncodableString{"foo", errors.New("TextUnmarshaler error")}, []byte("foo"), true}, - } { - t.Run(tt.name, func(t *testing.T) { - o, err := newValueEncoder(reflect.TypeOf(tt.i))(reflect.ValueOf(tt.i)) - if tt.shouldErr != (err != nil) { - t.Errorf("newValueEncoder(%s)() shouldErr expected %v, have %v (%v)", reflect.TypeOf(tt.i).Name(), tt.shouldErr, err != nil, err) - } - if !tt.shouldErr && !bytes.Equal(o, tt.o) { - t.Errorf("newValueEncoder(%s)() expected %v, have %v", reflect.TypeOf(tt.i).Name(), tt.o, o) - } - }) - } -} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth.go b/vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth.go deleted file mode 100644 index 80992559d..000000000 --- a/vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package fixedwidth provides encoding and decoding for fixed-width formatted Data. -package fixedwidth diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth_test.go b/vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth_test.go deleted file mode 100644 index de51609a3..000000000 --- a/vendor/github.com/ianlopshire/go-fixedwidth/fixedwidth_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package fixedwidth - -var ( - nilFloat64 *float64 - nilFloat32 *float32 - nilInt *int - nilString *string -) - -func float64p(v float64) *float64 { return &v } -func float32p(v float32) *float32 { return &v } -func intp(v int) *int { return &v } -func stringp(v string) *string { return &v } - -// EncodableString is a string that implements the encoding TextUnmarshaler and TextMarshaler interface. -// This is useful for testing. -type EncodableString struct { - S string - Err error -} - -// UnmarshalText implements encoding.TextUnmarshaler. -func (s *EncodableString) UnmarshalText(text []byte) error { - s.S = string(text) - return s.Err -} - -// MarshalText implements encoding.TextUnmarshaler. -func (s EncodableString) MarshalText() ([]byte, error) { - return []byte(s.S), s.Err -} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/tags.go b/vendor/github.com/ianlopshire/go-fixedwidth/tags.go deleted file mode 100644 index ba54e2de1..000000000 --- a/vendor/github.com/ianlopshire/go-fixedwidth/tags.go +++ /dev/null @@ -1,28 +0,0 @@ -package fixedwidth - -import ( - "strconv" - "strings" -) - -// parseTag splits a struct fields fixed tag into its start and end positions. -// If the tag is not valid, ok will be false. -func parseTag(tag string) (startPos, endPos int, ok bool) { - parts := strings.Split(tag, ",") - if len(parts) != 2 { - return startPos, endPos, false - } - - var err error - if startPos, err = strconv.Atoi(parts[0]); err != nil { - return startPos, endPos, false - } - if endPos, err = strconv.Atoi(parts[1]); err != nil { - return startPos, endPos, false - } - if startPos > endPos || (startPos == 0 && endPos == 0) { - return startPos, endPos, false - } - - return startPos, endPos, true -} diff --git a/vendor/github.com/ianlopshire/go-fixedwidth/tags_test.go b/vendor/github.com/ianlopshire/go-fixedwidth/tags_test.go deleted file mode 100644 index 4bea88c25..000000000 --- a/vendor/github.com/ianlopshire/go-fixedwidth/tags_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package fixedwidth - -import ( - "testing" -) - -func TestParseTag(t *testing.T) { - for _, tt := range []struct { - name string - tag string - startPos int - endPos int - ok bool - }{ - {"Valid Tag", "0,10", 0, 10, true}, - {"Valid Tag Single position", "5,5", 5, 5, true}, - {"Tag Empty", "", 0, 0, false}, - {"Tag Too short", "0", 0, 0, false}, - {"Tag Too Long", "2,10,11", 0, 0, false}, - {"StartPos Not Integer", "hello,3", 0, 0, false}, - {"EndPos Not Integer", "3,hello", 0, 0, false}, - {"Tag Contains a Space", "4, 11", 0, 0, false}, - {"Tag Interval Invalid", "14,5", 0, 0, false}, - {"Tag Both Positions Zero", "0,0", 0, 0, false}, - } { - t.Run(tt.name, func(t *testing.T) { - startPos, endPos, ok := parseTag(tt.tag) - if tt.ok != ok { - t.Errorf("parseTag() ok want %v, have %v", tt.ok, ok) - } - - // only check startPos and endPos if valid tags are expected - if tt.ok { - if tt.startPos != startPos { - t.Errorf("parseTag() startPos want %v, have %v", tt.startPos, startPos) - } - - if tt.endPos != endPos { - t.Errorf("parseTag() endPos want %v, have %v", tt.endPos, endPos) - } - } - }) - } -} From 88cc93719980eebc8c6bf0d7a88b8e4797af2e79 Mon Sep 17 00:00:00 2001 From: Taras Postument Date: Sun, 1 Apr 2018 11:07:34 +0300 Subject: [PATCH 5/9] working version of formatter v2 --- aws/tailers/stack_event.go | 58 ++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/aws/tailers/stack_event.go b/aws/tailers/stack_event.go index cf3d57220..9efe3ebd8 100644 --- a/aws/tailers/stack_event.go +++ b/aws/tailers/stack_event.go @@ -10,6 +10,7 @@ import ( "text/tabwriter" "time" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/configservice" "github.com/fatih/color" @@ -52,8 +53,8 @@ type stackEvent struct { ResourceType *string `width:"45,5"` LogicalResourceId *string `width:"20,5"` - PhysicalResourceId *string `width:"100,5"` - ResourceStatusReason *string `width:"100,5"` + PhysicalResourceId *string `width:"50,5"` + ResourceStatusReason *string `width:"50,5"` EventId *string } @@ -382,7 +383,10 @@ func (s *stackEvent) format(fil filters) []byte { tp := reflect.TypeOf(s).Elem() v := reflect.ValueOf(s).Elem() + fmt.Println(" Call Format") + buf := bytes.Buffer{} + var nextLine *stackEvent for _, f := range fil { field, ok := tp.FieldByName(filtersMapping[f]) if !ok { @@ -390,17 +394,17 @@ func (s *stackEvent) format(fil filters) []byte { } value := v.FieldByName(filtersMapping[f]) - split := strings.Split(field.Tag.Get("width"), ",") - if len(split) != 2 { + splt := strings.Split(field.Tag.Get("width"), ",") + if len(splt) != 2 { continue } - width, err := strconv.Atoi(split[0]) + width, err := strconv.Atoi(splt[0]) if err != nil { continue } - space, err := strconv.Atoi(split[1]) + space, err := strconv.Atoi(splt[1]) if err != nil { continue } @@ -421,19 +425,51 @@ func (s *stackEvent) format(fil filters) []byte { width += strings.Index(v, "m") + 1 + len("\x1b[0m") } - // crop string if it longer then defined width if len(v) > width { + if nextLine == nil { + nextLine = &stackEvent{} + } + if field.Name == "ResourceStatusReason" { + nextLine.ResourceStatusReason = aws.String(v[width:]) + } + if field.Name == "ResourceStatus" { + nextLine.ResourceStatus = aws.String(v[width:]) + } + v = v[:width] } buf.WriteString(v) - // fil the rest of the line space with " " - for i := len(v); i < width+space; i++ { - buf.WriteString(" ") - } + buf.WriteString(createSpaces(width + space - len(v))) + // totalWidth += width } buf.WriteRune('\n') + if nextLine != nil { + buf.Write(nextLine.format(fil)) + } return buf.Bytes() } + +func createSpaces(n int) string { + var buf = bytes.Buffer{} + for i := 0; i < n; i++ { + buf.WriteString(".") + } + + return buf.String() +} + +func split(buf []byte, lim int) [][]byte { + var chunk []byte + chunks := make([][]byte, 0, len(buf)/lim+1) + for len(buf) >= lim { + chunk, buf = buf[:lim], buf[lim:] + chunks = append(chunks, chunk) + } + if len(buf) > 0 { + chunks = append(chunks, buf[:len(buf)]) + } + return chunks +} From 621eca602e00c7fdb5f16de76392048b4d0b65f2 Mon Sep 17 00:00:00 2001 From: Taras Postument Date: Sun, 1 Apr 2018 11:34:20 +0300 Subject: [PATCH 6/9] more updates --- aws/tailers/stack_event.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/aws/tailers/stack_event.go b/aws/tailers/stack_event.go index 9efe3ebd8..fcfbc6e15 100644 --- a/aws/tailers/stack_event.go +++ b/aws/tailers/stack_event.go @@ -429,13 +429,8 @@ func (s *stackEvent) format(fil filters) []byte { if nextLine == nil { nextLine = &stackEvent{} } - if field.Name == "ResourceStatusReason" { - nextLine.ResourceStatusReason = aws.String(v[width:]) - } - if field.Name == "ResourceStatus" { - nextLine.ResourceStatus = aws.String(v[width:]) - } - + nv := reflect.ValueOf(nextLine).Elem() + nv.FieldByName(field.Name).Set(reflect.ValueOf(aws.String(v[width:]))) v = v[:width] } From 1d33cec22ac0861ce5f1028b4b90e1ec9e18ac7f Mon Sep 17 00:00:00 2001 From: Taras Postument Date: Sun, 1 Apr 2018 11:48:34 +0300 Subject: [PATCH 7/9] remove errbuf tabwriter --- aws/tailers/stack_event.go | 42 ++++++++++++++------------------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/aws/tailers/stack_event.go b/aws/tailers/stack_event.go index fcfbc6e15..29ca2ce7f 100644 --- a/aws/tailers/stack_event.go +++ b/aws/tailers/stack_event.go @@ -1,13 +1,13 @@ package awstailers import ( + "bufio" "bytes" "fmt" "io" "reflect" "strconv" "strings" - "text/tabwriter" "time" "github.com/aws/aws-sdk-go/aws" @@ -51,10 +51,10 @@ type stackEvent struct { Timestamp *string `width:"20,5"` ResourceStatus *string `width:"50,5"` ResourceType *string `width:"45,5"` - LogicalResourceId *string `width:"20,5"` + LogicalResourceId *string `width:"25,5"` - PhysicalResourceId *string `width:"50,5"` - ResourceStatusReason *string `width:"50,5"` + PhysicalResourceId *string `width:"40,5"` + ResourceStatusReason *string `width:"40,5"` EventId *string } @@ -151,12 +151,9 @@ func (t *stackEventTailer) Tail(w io.Writer) error { errBuf.WriteString("Failed events summary:\n") - // using tabwriter here, because we have all data - // and no need to stream it - errTab := tabwriter.NewWriter(&errBuf, 25, 8, 0, '\t', 0) - errTab.Write(f.header()) - t.deploymentStatus.failedEvents.printReverse(errTab, f) - errTab.Flush() + errBuf.Write(f.header()) + writer := bufio.NewWriter(&errBuf) + t.deploymentStatus.failedEvents.printReverse(writer, f) return fmt.Errorf(errBuf.String()) } @@ -383,11 +380,9 @@ func (s *stackEvent) format(fil filters) []byte { tp := reflect.TypeOf(s).Elem() v := reflect.ValueOf(s).Elem() - fmt.Println(" Call Format") - buf := bytes.Buffer{} var nextLine *stackEvent - for _, f := range fil { + for i, f := range fil { field, ok := tp.FieldByName(filtersMapping[f]) if !ok { continue @@ -409,6 +404,12 @@ func (s *stackEvent) format(fil filters) []byte { continue } + // no need of space in the last column + if i == len(fil)-1 { + width += space + space = 0 + } + var v string if !value.IsNil() { v = value.Elem().String() @@ -450,21 +451,8 @@ func (s *stackEvent) format(fil filters) []byte { func createSpaces(n int) string { var buf = bytes.Buffer{} for i := 0; i < n; i++ { - buf.WriteString(".") + buf.WriteString(" ") } return buf.String() } - -func split(buf []byte, lim int) [][]byte { - var chunk []byte - chunks := make([][]byte, 0, len(buf)/lim+1) - for len(buf) >= lim { - chunk, buf = buf[:lim], buf[lim:] - chunks = append(chunks, chunk) - } - if len(buf) > 0 { - chunks = append(chunks, buf[:len(buf)]) - } - return chunks -} From 9be605f1c0c0df6f372f64b618edcc7dfb3a8273 Mon Sep 17 00:00:00 2001 From: Taras Postument Date: Mon, 9 Apr 2018 23:29:44 +0300 Subject: [PATCH 8/9] update tailer func --- aws/tailers/stack_event.go | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/aws/tailers/stack_event.go b/aws/tailers/stack_event.go index 29ca2ce7f..785ad5dca 100644 --- a/aws/tailers/stack_event.go +++ b/aws/tailers/stack_event.go @@ -154,7 +154,7 @@ func (t *stackEventTailer) Tail(w io.Writer) error { errBuf.Write(f.header()) writer := bufio.NewWriter(&errBuf) t.deploymentStatus.failedEvents.printReverse(writer, f) - + writer.Flush() return fmt.Errorf(errBuf.String()) } return nil @@ -321,13 +321,23 @@ func (e stackEvents) printReverse(w io.Writer, f filters) error { } func (f filters) header() []byte { + //// TODO: bold text still shifts the columns, need to figure out whats wrong + // s := &stackEvent{ + // Timestamp: func() *string { t := color.New(color.Bold).Sprintf("Timestamp"); return &t }(), + // ResourceStatus: func() *string { t := color.New(color.Bold).Sprintf("Status"); return &t }(), + // LogicalResourceId: func() *string { t := color.New(color.Bold).Sprintf("Logical ID"); return &t }(), + // PhysicalResourceId: func() *string { t := color.New(color.Bold).Sprintf("Physical ID"); return &t }(), + // ResourceStatusReason: func() *string { t := color.New(color.Bold).Sprintf("Status Reason"); return &t }(), + // ResourceType: func() *string { t := color.New(color.Bold).Sprintf("Type"); return &t }(), + // } + s := &stackEvent{ - Timestamp: func() *string { t := color.New(color.Bold).Sprintf("Timestamp"); return &t }(), - ResourceStatus: func() *string { t := color.New(color.Bold).Sprintf("Status"); return &t }(), - LogicalResourceId: func() *string { t := color.New(color.Bold).Sprintf("Logical ID"); return &t }(), - PhysicalResourceId: func() *string { t := color.New(color.Bold).Sprintf("Physical ID"); return &t }(), - ResourceStatusReason: func() *string { t := color.New(color.Bold).Sprintf("Status Reason"); return &t }(), - ResourceType: func() *string { t := color.New(color.Bold).Sprintf("Type"); return &t }(), + Timestamp: func() *string { t := "Timestamp"; return &t }(), + ResourceStatus: func() *string { t := "Status"; return &t }(), + LogicalResourceId: func() *string { t := "Logical ID"; return &t }(), + PhysicalResourceId: func() *string { t := "Physical ID"; return &t }(), + ResourceStatusReason: func() *string { t := "Status Reason"; return &t }(), + ResourceType: func() *string { t := "Type"; return &t }(), } return s.format(f) @@ -352,10 +362,6 @@ func (s *stackEvent) isFailed() bool { return (s.ResourceStatus != nil && (strings.HasSuffix(*s.ResourceStatus, StackEventFailed) || *s.ResourceStatus == cloudformation.StackStatusUpdateRollbackInProgress)) } -func (s *stackEvent) fromCFEvent() bool { - return (s.ResourceStatus != nil && (strings.HasSuffix(*s.ResourceStatus, StackEventFailed) || *s.ResourceStatus == cloudformation.StackStatusUpdateRollbackInProgress)) -} - func (s *stackEventTailer) cancelStackUpdate(cfn *awsservices.Cloudformation) error { inp := &cloudformation.CancelUpdateStackInput{StackName: &s.stackName} _, err := cfn.CancelUpdateStack(inp) @@ -365,7 +371,7 @@ func (s *stackEventTailer) cancelStackUpdate(cfn *awsservices.Cloudformation) er func NewStackEvent(e *cloudformation.StackEvent) stackEvent { return stackEvent{ Timestamp: func() *string { t := e.Timestamp.Format(time.RFC3339); return &t }(), - ResourceStatus: colorizeResourceStatus(*e.ResourceStatus), + ResourceStatus: e.ResourceStatus, ResourceType: e.ResourceType, LogicalResourceId: e.LogicalResourceId, PhysicalResourceId: e.PhysicalResourceId, @@ -380,6 +386,10 @@ func (s *stackEvent) format(fil filters) []byte { tp := reflect.TypeOf(s).Elem() v := reflect.ValueOf(s).Elem() + if s.ResourceStatus != nil { + s.ResourceStatus = colorizeResourceStatus(*s.ResourceStatus) + } + buf := bytes.Buffer{} var nextLine *stackEvent for i, f := range fil { @@ -423,6 +433,8 @@ func (s *stackEvent) format(fil filters) []byte { // and results in text shift // so we need to increase column width a bit // colored string looks like: "\x1b[31mText\x1b[0m" + // TODO: looks like this doesn't helps, if one line has the + // more then one colored column (like header) width += strings.Index(v, "m") + 1 + len("\x1b[0m") } From e0a1f66f18132bc551d5348a7834338e71c2bcc7 Mon Sep 17 00:00:00 2001 From: Taras Postument Date: Mon, 9 Apr 2018 23:43:30 +0300 Subject: [PATCH 9/9] cleanup --- aws/tailers/stack_event.go | 1 - 1 file changed, 1 deletion(-) diff --git a/aws/tailers/stack_event.go b/aws/tailers/stack_event.go index 785ad5dca..869193893 100644 --- a/aws/tailers/stack_event.go +++ b/aws/tailers/stack_event.go @@ -450,7 +450,6 @@ func (s *stackEvent) format(fil filters) []byte { buf.WriteString(v) // fil the rest of the line space with " " buf.WriteString(createSpaces(width + space - len(v))) - // totalWidth += width } buf.WriteRune('\n')