pax_global_header00006660000000000000000000000064142454055310014515gustar00rootroot0000000000000052 comment=1fe68c98cfd8958829d83e61511c847d47108076 neat-1.3.11/000077500000000000000000000000001424540553100125275ustar00rootroot00000000000000neat-1.3.11/.github/000077500000000000000000000000001424540553100140675ustar00rootroot00000000000000neat-1.3.11/.github/dependabot.yml000066400000000000000000000001771424540553100167240ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: "/" schedule: interval: daily open-pull-requests-limit: 10 neat-1.3.11/.github/workflows/000077500000000000000000000000001424540553100161245ustar00rootroot00000000000000neat-1.3.11/.github/workflows/build.yml000066400000000000000000000030351424540553100177470ustar00rootroot00000000000000--- name: Build and Tests on: push: tags-ignore: - '**' branches: - main pull_request: branches: - main jobs: build: name: Build and Tests runs-on: ubuntu-latest steps: - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.17.x - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Verify Go Modules Setup run: | go mod verify - name: Build Go Code run: | go build ./... - name: Sanity Check (go vet) run: | go vet ./... - name: Sanity Check (ineffassign) run: | go get github.com/gordonklaus/ineffassign ineffassign ./... - name: Sanity Check (golint) run: | go get golang.org/x/lint/golint golint ./... - name: Sanity Check (misspell) run: | go get github.com/client9/misspell/cmd/misspell find . -type f | xargs misspell -source=text -error - name: Sanity Check (staticcheck) run: | go get honnef.co/go/tools/cmd/staticcheck staticcheck ./... - name: Run Go Unit Tests run: | go get github.com/onsi/ginkgo/ginkgo github.com/onsi/gomega/... ginkgo -randomizeAllSpecs -randomizeSuites -failOnPending -nodes=1 -compilers=1 -race -trace -cover - name: Upload Code Coverage Profile uses: codecov/codecov-action@v1 with: files: ./*.coverprofile flags: unittests fail_ci_if_error: true verbose: false neat-1.3.11/.gitignore000066400000000000000000000000171424540553100145150ustar00rootroot00000000000000*.coverprofile neat-1.3.11/LICENSE000066400000000000000000000020621424540553100135340ustar00rootroot00000000000000MIT License Copyright (c) 2019 The Homeport Team 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. neat-1.3.11/README.md000066400000000000000000000017551424540553100140160ustar00rootroot00000000000000# neat [![License](https://img.shields.io/github/license/gonvenience/neat.svg)](https://github.com/gonvenience/neat/blob/main/LICENSE) [![Go Report Card](https://goreportcard.com/badge/github.com/gonvenience/neat)](https://goreportcard.com/report/github.com/gonvenience/neat) [![Build and Tests](https://github.com/gonvenience/neat/workflows/Build%20and%20Tests/badge.svg)](https://github.com/gonvenience/neat/actions?query=workflow%3A%22Build+and+Tests%22) [![Codecov](https://img.shields.io/codecov/c/github/gonvenience/neat/main.svg)](https://codecov.io/gh/gonvenience/neat) [![PkgGoDev](https://pkg.go.dev/badge/github.com/gonvenience/neat)](https://pkg.go.dev/github.com/gonvenience/neat) [![Release](https://img.shields.io/github/release/gonvenience/neat.svg)](https://github.com/gonvenience/neat/releases/latest) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gonvenience/neat) Golang package with convenience functions to create neat strings like colored YAML output neat-1.3.11/box.go000066400000000000000000000113321424540553100136460ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat import ( "bufio" "bytes" "fmt" "io" "strings" colorful "github.com/lucasb-eyer/go-colorful" "github.com/gonvenience/bunt" "github.com/gonvenience/term" ) // BoxStyle represents a styling option for a content box type BoxStyle func(*boxOptions) type boxOptions struct { headlineColor *colorful.Color contentColor *colorful.Color headlineStyles []bunt.StyleOption noClosingEndOfLine bool noLineWrap bool } // HeadlineColor sets the color of the headline text func HeadlineColor(color colorful.Color) BoxStyle { return func(options *boxOptions) { options.headlineColor = &color } } // HeadlineStyle sets the style to be used for the headline text func HeadlineStyle(style bunt.StyleOption) BoxStyle { return func(options *boxOptions) { options.headlineStyles = append(options.headlineStyles, style) } } // ContentColor sets the color of the content text func ContentColor(color colorful.Color) BoxStyle { return func(options *boxOptions) { options.contentColor = &color } } // NoFinalEndOfLine specifies that the rendering does not add a closing linefeed func NoFinalEndOfLine() BoxStyle { return func(options *boxOptions) { options.noClosingEndOfLine = true } } // NoLineWrap disables line wrapping in the content box func NoLineWrap() BoxStyle { return func(options *boxOptions) { options.noLineWrap = true } } // ContentBox creates a string for the terminal where content is printed inside // a simple box shape. func ContentBox(headline string, content string, opts ...BoxStyle) string { var buf bytes.Buffer Box(&buf, headline, strings.NewReader(content), opts...) return buf.String() } // Box writes the provided content in a simple box shape to given writer func Box(out io.Writer, headline string, content io.Reader, opts ...BoxStyle) { var ( beginning = "╭" prefix = "│" lastline = "╵" linewritten = false ) // Process all provided box style options options := &boxOptions{} for _, f := range opts { f(options) } // Apply headline color if it is set if options.headlineColor != nil { for _, pointer := range []*string{&beginning, &headline, &prefix, &lastline} { *pointer = bunt.Style(*pointer, bunt.Foreground(*options.headlineColor), ) } } // Apply headline styles if they are set for _, style := range options.headlineStyles { headline = bunt.Style(headline, style) } var processText = func(text string) []string { if options.noLineWrap { return []string{text} } words := strings.Fields(strings.TrimSpace(text)) if len(words) == 0 { return []string{text} } var ( buf bytes.Buffer lines = []string{} lineWidth = term.GetTerminalWidth() - len(prefix) ) buf.WriteString(words[0]) for _, word := range words[1:] { if len(word)+1 > lineWidth-buf.Len() { lines = append(lines, buf.String()) buf.Reset() buf.WriteString(word) } else { fmt.Fprint(&buf, " ", word) } } return append(lines, buf.String()) } // Process each line of the content and apply styles if necessary scanner := bufio.NewScanner(content) for scanner.Scan() { text := scanner.Text() if !linewritten { // Write the headline string including the corner item fmt.Fprintf(out, "%s %s\n", beginning, headline) } for _, line := range processText(text) { if options.contentColor != nil { line = bunt.Style(line, bunt.Foreground(*options.contentColor)) } fmt.Fprintf(out, "%s %s\n", prefix, line) } linewritten = true } if linewritten { fmt.Fprint(out, lastline) // If not configured otherwise, end with a linefeed if !options.noClosingEndOfLine { fmt.Fprintln(out) } } } neat-1.3.11/box_test.go000066400000000000000000000150331424540553100147070ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat_test import ( "bytes" "io" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/bunt" . "github.com/gonvenience/neat" . "github.com/gonvenience/term" ) var _ = Describe("content box", func() { BeforeEach(func() { SetColorSettings(ON, ON) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) Context("rendering content boxes", func() { var ( headline = "headline" content = `multi line content ` ) It("should create a simply styled content box", func() { Expect("\n" + ContentBox(headline, content)).To(BeEquivalentTo(Sprintf(` ╭ headline │ multi │ line │ content ╵ `))) }) It("should create a simply styled content box with bold headline", func() { Expect("\n" + ContentBox(headline, content, HeadlineStyle(Bold()), )).To(BeEquivalentTo(Sprintf(` ╭ *headline* │ multi │ line │ content ╵ `))) }) It("should create a simply styled content box with italic, underline, and bold headline", func() { Expect("\n" + ContentBox(headline, content, HeadlineStyle(Italic()), HeadlineStyle(Underline()), HeadlineStyle(Bold()), )).To(BeEquivalentTo(Sprintf(` ╭ _*~headline~*_ │ multi │ line │ content ╵ `))) }) It("should create styled content box with headline colors", func() { Expect("\n" + ContentBox(headline, content, HeadlineColor(DodgerBlue), )).To(BeEquivalentTo(Sprintf(` DodgerBlue{╭} DodgerBlue{headline} DodgerBlue{│} multi DodgerBlue{│} line DodgerBlue{│} content DodgerBlue{╵} `))) }) It("should create styled content box with content colors", func() { Expect("\n" + ContentBox(headline, content, ContentColor(DimGray), )).To(BeEquivalentTo(Sprintf(` ╭ headline │ DimGray{multi} │ DimGray{line} │ DimGray{content} ╵ `))) }) It("should create styled content box with headline and content colors", func() { Expect("\n" + ContentBox(headline, content, HeadlineColor(DodgerBlue), ContentColor(DimGray), )).To(BeEquivalentTo(Sprintf(` DodgerBlue{╭} DodgerBlue{headline} DodgerBlue{│} DimGray{multi} DodgerBlue{│} DimGray{line} DodgerBlue{│} DimGray{content} DodgerBlue{╵} `))) }) It("should create a content box with no trailing line feed", func() { Expect("\n" + ContentBox( headline, content, NoFinalEndOfLine(), )).To(BeEquivalentTo(Sprintf(` ╭ headline │ multi │ line │ content ╵`))) }) }) Context("rendering content boxes with already colored content", func() { setupTestStrings := func() (string, string) { return Sprintf("CornflowerBlue{~headline~}"), Sprintf(`Red{*multi*} Green{_line_} Blue{~content~} `) } It("should preserve already existing colors and text emphasis", func() { headline, content := setupTestStrings() Expect("\n" + ContentBox(headline, content)).To(BeEquivalentTo(Sprintf(` ╭ CornflowerBlue{~headline~} │ Red{*multi*} │ Green{_line_} │ Blue{~content~} ╵ `))) }) It("should overwrite existing headline color if it is specified", func() { headline, content := setupTestStrings() Expect("\n" + ContentBox(headline, content, HeadlineColor(DimGray), )).To(BeEquivalentTo(Sprintf(` DimGray{╭} DimGray{~headline~} DimGray{│} Red{*multi*} DimGray{│} Green{_line_} DimGray{│} Blue{~content~} DimGray{╵} `))) }) It("should overwrite existing content color if it is specified", func() { headline, content := setupTestStrings() Expect("\n" + ContentBox(headline, content, ContentColor(DimGray), )).To(BeEquivalentTo(Sprintf(` ╭ CornflowerBlue{~headline~} │ DimGray{*multi*} │ DimGray{_line_} │ DimGray{~content~} ╵ `))) }) }) Context("using reader directly", func() { It("should create a box using a reader", func() { r, w := io.Pipe() go func() { _, _ = w.Write([]byte("multi\n")) _, _ = w.Write([]byte("line\n")) _, _ = w.Write([]byte("content\n")) _ = w.Close() }() var buf bytes.Buffer Box(&buf, "headline", r) Expect("\n" + buf.String()).To(BeEquivalentTo(Sprintf(` ╭ headline │ multi │ line │ content ╵ `))) }) It("should create no box if no content could be obtained from the reader", func() { r, w := io.Pipe() go func() { w.Close() }() var buf bytes.Buffer Box(&buf, "headline", r) Expect(len(buf.String())).To(BeIdenticalTo(0)) }) }) Context("using line wrap", func() { var tmp int BeforeEach(func() { tmp = FixedTerminalWidth FixedTerminalWidth = 80 }) AfterEach(func() { FixedTerminalWidth = tmp }) It("should wrap lines that are too long", func() { Expect("\n" + ContentBox( "headline", "content with a very long first line, that is most likely an error message with a lot of context or similar", )).To(BeEquivalentTo(Sprintf(` ╭ headline │ content with a very long first line, that is most likely an error message │ with a lot of context or similar ╵ `))) }) It("should not wrap long lines if wrapping is disabled", func() { Expect("\n" + ContentBox( "headline", "content with a very long first line, that is most likely an error message with a lot of context or similar", NoLineWrap(), )).To(BeEquivalentTo(Sprintf(` ╭ headline │ content with a very long first line, that is most likely an error message with a lot of context or similar ╵ `))) }) It("should not fail with empty lines", func() { Expect("\n" + ContentBox( "headline", " ", )).To(BeEquivalentTo(Sprintf(` ╭ headline │ ╵ `))) }) }) }) neat-1.3.11/errors.go000066400000000000000000000044501424540553100143750ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat import ( "fmt" "io" "strings" "github.com/gonvenience/bunt" "github.com/gonvenience/wrap" ) // PrintError prints the provided error to stdout func PrintError(err error) { fmt.Print(SprintError(err)) } // FprintError prints the provided error to the provided writer func FprintError(w io.Writer, err error) { fmt.Fprint(w, SprintError(err)) } // SprintError prints the provided error as a string func SprintError(err error) string { switch e := err.(type) { case wrap.ContextError: var content string switch e.Cause().(type) { case wrap.ContextError: content = SprintError(e.Cause()) default: content = e.Cause().Error() } return ContentBox( fmt.Sprintf("Error: %s", e.Context()), content, HeadlineColor(bunt.OrangeRed), ContentColor(bunt.Red), ) default: if parts := strings.SplitN(err.Error(), ":", 2); len(parts) == 2 { message, cause := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) return ContentBox( fmt.Sprintf("Error: %s", message), cause, HeadlineColor(bunt.OrangeRed), ContentColor(bunt.Red), ) } return ContentBox( "Error", err.Error(), HeadlineColor(bunt.OrangeRed), ContentColor(bunt.Red), ) } } neat-1.3.11/errors_test.go000066400000000000000000000071221424540553100154330ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat_test import ( "bufio" "bytes" "fmt" "io" "os" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/pkg/errors" . "github.com/gonvenience/bunt" . "github.com/gonvenience/neat" "github.com/gonvenience/wrap" ) var _ = Describe("error rendering", func() { BeforeEach(func() { SetColorSettings(ON, ON) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) Context("rendering errors", func() { It("should render a context error using a box", func() { context := fmt.Sprintf("unable to start %s", "Z") cause := fmt.Errorf("failed to load X and Y") err := wrap.Error(cause, context) Expect(SprintError(err)).To( BeEquivalentTo(ContentBox( "Error: "+context, cause.Error(), HeadlineColor(OrangeRed), ContentColor(Red), ))) }) It("should render to a writer", func() { buf := bytes.Buffer{} out := bufio.NewWriter(&buf) FprintError(out, fmt.Errorf("failed to do X")) out.Flush() Expect(buf.String()).To( BeEquivalentTo(ContentBox( "Error", "failed to do X", HeadlineColor(OrangeRed), ContentColor(Red), ))) }) It("should render to stdout, too", func() { captureStdout := func(f func()) string { r, w, err := os.Pipe() Expect(err).ToNot(HaveOccurred()) tmp := os.Stdout defer func() { os.Stdout = tmp }() os.Stdout = w f() w.Close() var buf bytes.Buffer _, err = io.Copy(&buf, r) Expect(err).ToNot(HaveOccurred()) return buf.String() } Expect(captureStdout(func() { PrintError(fmt.Errorf("failed to do X")) })).To( BeEquivalentTo(ContentBox( "Error", "failed to do X", HeadlineColor(OrangeRed), ContentColor(Red), ))) }) It("should render a context error inside a context error", func() { root := fmt.Errorf("unable to load X") cause := wrap.Errorf(root, "failed to start Y") context := "cannot process Z" err := wrap.Error(cause, context) Expect(SprintError(err)).To( BeEquivalentTo(ContentBox( "Error: "+context, SprintError(cause), HeadlineColor(OrangeRed), ContentColor(Red), ))) }) It("should render github.com/pkg/errors package errors", func() { message := "unable to start Z" cause := fmt.Errorf("failed to load X and Y") err := errors.Wrap(cause, message) Expect(SprintError(err)).To( BeEquivalentTo(ContentBox( "Error: "+message, cause.Error(), HeadlineColor(OrangeRed), ContentColor(Red), ))) }) }) }) neat-1.3.11/go.mod000066400000000000000000000016401424540553100136360ustar00rootroot00000000000000module github.com/gonvenience/neat go 1.17 require ( github.com/gonvenience/bunt v1.3.3 github.com/gonvenience/term v1.0.2 github.com/gonvenience/wrap v1.1.1 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.19.0 github.com/pkg/errors v0.9.1 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/nxadm/tail v1.4.8 // indirect golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect ) neat-1.3.11/go.sum000066400000000000000000000327071424540553100136730ustar00rootroot00000000000000github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gonvenience/bunt v1.3.3 h1:a751qSbJIgWGbazGYr9hyuudOg7wMHh2m4JjE3tfURE= github.com/gonvenience/bunt v1.3.3/go.mod h1:XjlODZ8sTL9jQs9c4Kj1ZBTrHSsbUGdoRy7ROCtw8nU= github.com/gonvenience/term v1.0.1/go.mod h1:TrQEhxBNE/ng5kTV+S0OvQowTDJSfhlBeZbcOmTR6qI= github.com/gonvenience/term v1.0.2 h1:qKa2RydbWIrabGjR/fegJwpW5m+JvUwFL8mLhHzDXn0= github.com/gonvenience/term v1.0.2/go.mod h1:wThTR+3MzWtWn7XGVW6qQ65uaVf8GHED98KmwpuEQeo= github.com/gonvenience/wrap v1.1.1 h1:7cMuyMtTdUe2aObp760CzXo/wOmFaY7JXsIksu8NYYc= github.com/gonvenience/wrap v1.1.1/go.mod h1:u27bruNdIB6U5nnR0qq+GRRKbAJzEqC0H3DBHxl7YNM= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 h1:BXxTozrOU8zgC5dkpn3J6NTRdoP+hjok/e+ACr4Hibk= github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3/go.mod h1:x1uk6vxTiVuNt6S5R2UYgdhpj3oKojXvOXauHZ7dEnI= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.0/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= neat-1.3.11/json_test.go000066400000000000000000000032711424540553100150710ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat_test import ( yamlv2 "gopkg.in/yaml.v2" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/neat" ) var _ = Describe("YAML to JSON tests", func() { Context("Processing valid YAML input", func() { It("should convert YAML to JSON", func() { var content yamlv2.MapSlice if err := yamlv2.Unmarshal([]byte(`--- name: foobar list: - A - B - C `), &content); err != nil { Fail(err.Error()) } result, err := ToJSONString(content) Expect(err).To(BeNil()) Expect(result).To(Equal(`{"name": "foobar", "list": ["A", "B", "C"]}`)) }) }) }) neat-1.3.11/neat.go000066400000000000000000000031541424540553100140100ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. /* Package neat provides convenience functions to create neat strings like colored YAML output (YAML Marshaller that supports true color output) or nicely spaced tables for the terminal. The `ToYAML` function returns neat looking YAML string output using text highlighting with emphasis, colors, and indent helper guide lines to create pleasing and easy to read YAML. The `Table` function returns a neat looking table based on a two-dimensional array which is rendered with spaces to have well aligned columns. */ package neat neat-1.3.11/neat_suite_test.go000066400000000000000000000027001424540553100162540ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat_test import ( "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" yamlv3 "gopkg.in/yaml.v3" ) func TestCore(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "neat suite") } func yml(in string) *yamlv3.Node { var node yamlv3.Node err := yamlv3.Unmarshal([]byte(in), &node) Expect(err).To(BeNil()) return &node } neat-1.3.11/output.go000066400000000000000000000115771424540553100144310ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat import ( "bufio" "bytes" "fmt" "strings" colorful "github.com/lucasb-eyer/go-colorful" yamlv2 "gopkg.in/yaml.v2" yamlv3 "gopkg.in/yaml.v3" "github.com/gonvenience/bunt" ) // DefaultColorSchema is a prepared usable color schema for the neat output // processor which is loosly based upon the colors used by Atom var DefaultColorSchema = map[string]colorful.Color{ "documentStart": bunt.LightSlateGray, "keyColor": bunt.IndianRed, "indentLineColor": {R: 0.14, G: 0.14, B: 0.14}, "scalarDefaultColor": bunt.PaleGreen, "boolColor": bunt.Moccasin, "floatColor": bunt.Orange, "intColor": bunt.MediumPurple, "multiLineTextColor": bunt.Aquamarine, "nullColor": bunt.DarkOrange, "binaryColor": bunt.Aqua, "emptyStructures": bunt.PaleGoldenrod, "commentColor": bunt.DimGray, "anchorColor": bunt.CornflowerBlue, } // OutputProcessor provides the functionality to output neat YAML strings using // colors and text emphasis type OutputProcessor struct { data *bytes.Buffer out *bufio.Writer colorSchema *map[string]colorful.Color useIndentLines bool boldKeys bool } // NewOutputProcessor creates a new output processor including the required // internals using the provided preferences func NewOutputProcessor(useIndentLines bool, boldKeys bool, colorSchema *map[string]colorful.Color) *OutputProcessor { bytesBuffer := &bytes.Buffer{} writer := bufio.NewWriter(bytesBuffer) // Only use indent lines in color mode if !bunt.UseColors() { useIndentLines = false } return &OutputProcessor{ data: bytesBuffer, out: writer, useIndentLines: useIndentLines, boldKeys: boldKeys, colorSchema: colorSchema, } } // Deprecated: Use colorizef instead func (p *OutputProcessor) colorize(text string, colorName string) string { if p.colorSchema != nil { if value, ok := (*p.colorSchema)[colorName]; ok { return bunt.Style(text, bunt.Foreground(value)) } } return text } func (p *OutputProcessor) colorizef(colorName string, format string, a ...interface{}) string { var text = fmt.Sprintf(format, a...) if p.colorSchema != nil { if value, ok := (*p.colorSchema)[colorName]; ok { return bunt.Style(text, bunt.Foreground(value)) } } return text } func (p *OutputProcessor) determineColorByType(obj interface{}) string { color := "scalarDefaultColor" switch t := obj.(type) { case *yamlv3.Node: switch t.Tag { case "!!str": if len(strings.Split(strings.TrimSpace(t.Value), "\n")) > 1 { color = "multiLineTextColor" } case "!!int": color = "intColor" case "!!float": color = "floatColor" case "!!bool": color = "boolColor" case "!!null": color = "nullColor" } case bool: color = "boolColor" case float32, float64: color = "floatColor" case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr: color = "intColor" case string: if len(strings.Split(strings.TrimSpace(t), "\n")) > 1 { color = "multiLineTextColor" } } return color } func (p *OutputProcessor) isScalar(obj interface{}) bool { switch tobj := obj.(type) { case *yamlv3.Node: return tobj.Kind == yamlv3.ScalarNode case yamlv2.MapSlice, []interface{}, []yamlv2.MapSlice: return false default: return true } } func (p *OutputProcessor) simplify(list []yamlv2.MapSlice) []interface{} { result := make([]interface{}, len(list)) for idx, value := range list { result[idx] = value } return result } func (p *OutputProcessor) prefixAdd() string { if p.useIndentLines { return p.colorize("│ ", "indentLineColor") } return p.colorize(" ", "indentLineColor") } func followAlias(node *yamlv3.Node) *yamlv3.Node { if node != nil && node.Alias != nil { return followAlias(node.Alias) } return node } neat-1.3.11/output_json.go000066400000000000000000000234501424540553100154530ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat import ( "encoding/json" "fmt" "strconv" "strings" "time" yamlv2 "gopkg.in/yaml.v2" yamlv3 "gopkg.in/yaml.v3" "github.com/gonvenience/bunt" ) // ToJSONString marshals the provided object into JSON with text decorations // and is basically just a convenience function to create the output processor // and call its `ToJSON` function. func ToJSONString(obj interface{}) (string, error) { return NewOutputProcessor(true, true, &DefaultColorSchema).ToCompactJSON(obj) } // ToJSON processes the provided input object and tries to neatly output it as // human readable JSON honoring the preferences provided to the output processor func (p *OutputProcessor) ToJSON(obj interface{}) (string, error) { return p.neatJSON("", obj) } // ToCompactJSON processed the provided input object and tries to create a as // compact as possible output func (p *OutputProcessor) ToCompactJSON(obj interface{}) (string, error) { switch tobj := obj.(type) { case *yamlv3.Node: return p.ToCompactJSON(*tobj) case yamlv3.Node: switch tobj.Kind { case yamlv3.DocumentNode: return p.ToCompactJSON(tobj.Content[0]) case yamlv3.MappingNode: tmp := []string{} for i := 0; i < len(tobj.Content); i += 2 { k, v := tobj.Content[i], tobj.Content[i+1] key, err := p.ToCompactJSON(k) if err != nil { return "", err } value, err := p.ToCompactJSON(v) if err != nil { return "", err } tmp = append(tmp, fmt.Sprintf("%s: %s", key, value)) } return fmt.Sprintf("{%s}", strings.Join(tmp, ", ")), nil case yamlv3.SequenceNode: tmp := []string{} for _, e := range tobj.Content { entry, err := p.ToCompactJSON(e) if err != nil { return "", err } tmp = append(tmp, entry) } return fmt.Sprintf("[%s]", strings.Join(tmp, ", ")), nil case yamlv3.ScalarNode: obj, err := cast(tobj) if err != nil { return "", err } bytes, err := json.Marshal(obj) if err != nil { return "", err } return string(bytes), nil } case []interface{}: result := make([]string, 0) for _, i := range tobj { value, err := p.ToCompactJSON(i) if err != nil { return "", err } result = append(result, value) } return fmt.Sprintf("[%s]", strings.Join(result, ", ")), nil case yamlv2.MapSlice: result := make([]string, 0) for _, i := range tobj { value, err := p.ToCompactJSON(i) if err != nil { return "", err } result = append(result, value) } return fmt.Sprintf("{%s}", strings.Join(result, ", ")), nil case yamlv2.MapItem: key, keyError := p.ToCompactJSON(tobj.Key) if keyError != nil { return "", keyError } value, valueError := p.ToCompactJSON(tobj.Value) if valueError != nil { return "", valueError } return fmt.Sprintf("%s: %s", key, value), nil } bytes, err := json.Marshal(obj) if err != nil { return "", err } return string(bytes), nil } func (p *OutputProcessor) neatJSON(prefix string, obj interface{}) (string, error) { var err error switch t := obj.(type) { case yamlv3.Node: err = p.neatJSONofNode(prefix, &t) case *yamlv3.Node: err = p.neatJSONofNode(prefix, t) case yamlv2.MapSlice: err = p.neatJSONofYAMLMapSlice(prefix, t) case []interface{}: err = p.neatJSONofSlice(prefix, t) default: err = p.neatJSONofScalar(prefix, obj) } if err != nil { return "", err } p.out.Flush() return p.data.String(), nil } func (p *OutputProcessor) neatJSONofNode(prefix string, node *yamlv3.Node) error { var ( optionalLineBreak = func() string { switch node.Style { case yamlv3.FlowStyle: return "" } return "\n" } optionalIndentPrefix = func() string { switch node.Style { case yamlv3.FlowStyle: return "" } return prefix + p.prefixAdd() } optionalPrefixBeforeEnd = func() string { switch node.Style { case yamlv3.FlowStyle: return "" } return prefix } ) switch node.Kind { case yamlv3.DocumentNode: return p.neatJSONofNode(prefix, node.Content[0]) case yamlv3.MappingNode: if len(node.Content) == 0 { fmt.Fprint(p.out, p.colorize("{}", "emptyStructures")) return nil } bunt.Fprint(p.out, "*{*", optionalLineBreak()) for i := 0; i < len(node.Content); i += 2 { k, v := followAlias(node.Content[i]), followAlias(node.Content[i+1]) fmt.Fprint(p.out, optionalIndentPrefix(), p.colorize(`"`+k.Value+`"`, "keyColor"), ": ", ) if p.isScalar(v) { p.neatJSON("", v) } else { p.neatJSON(prefix+p.prefixAdd(), v) } if i < len(node.Content)-2 { fmt.Fprint(p.out, ",") } fmt.Fprint(p.out, optionalLineBreak()) } bunt.Fprint(p.out, optionalPrefixBeforeEnd(), "*}*") case yamlv3.SequenceNode: if len(node.Content) == 0 { fmt.Fprint(p.out, p.colorize("[]", "emptyStructures")) return nil } bunt.Fprint(p.out, "*[*", optionalLineBreak()) for i := range node.Content { entry := followAlias(node.Content[i]) if p.isScalar(entry) { p.neatJSON(optionalIndentPrefix(), entry) } else { fmt.Fprint(p.out, prefix, p.prefixAdd()) p.neatJSON(prefix+p.prefixAdd(), entry) } if i < len(node.Content)-1 { fmt.Fprint(p.out, ",") } fmt.Fprint(p.out, optionalLineBreak()) } bunt.Fprint(p.out, optionalPrefixBeforeEnd(), "*]*") case yamlv3.ScalarNode: obj, err := cast(*node) if err != nil { return err } bytes, err := json.Marshal(obj) if err != nil { return err } fmt.Fprint(p.out, prefix, p.colorize( string(bytes), p.determineColorByType(node), )) } return nil } func (p *OutputProcessor) neatJSONofYAMLMapSlice(prefix string, mapslice yamlv2.MapSlice) error { if len(mapslice) == 0 { _, _ = p.out.WriteString(p.colorize("{}", "emptyStructures")) return nil } _, _ = p.out.WriteString(bunt.Style("{", bunt.Bold())) _, _ = p.out.WriteString("\n") for idx, mapitem := range mapslice { keyString := fmt.Sprintf("\"%v\": ", mapitem.Key) _, _ = p.out.WriteString(prefix + p.prefixAdd()) _, _ = p.out.WriteString(p.colorize(keyString, "keyColor")) if p.isScalar(mapitem.Value) { if err := p.neatJSONofScalar("", mapitem.Value); err != nil { return err } } else { p.neatJSON(prefix+p.prefixAdd(), mapitem.Value) } if idx < len(mapslice)-1 { _, _ = p.out.WriteString(",") } _, _ = p.out.WriteString("\n") } _, _ = p.out.WriteString(prefix) _, _ = p.out.WriteString(bunt.Style("}", bunt.Bold())) return nil } func (p *OutputProcessor) neatJSONofSlice(prefix string, list []interface{}) error { if len(list) == 0 { _, _ = p.out.WriteString(p.colorize("[]", "emptyStructures")) return nil } _, _ = p.out.WriteString(bunt.Style("[", bunt.Bold())) _, _ = p.out.WriteString("\n") for idx, value := range list { if p.isScalar(value) { if err := p.neatJSONofScalar(prefix+p.prefixAdd(), value); err != nil { return err } } else { _, _ = p.out.WriteString(prefix + p.prefixAdd()) p.neatJSON(prefix+p.prefixAdd(), value) } if idx < len(list)-1 { _, _ = p.out.WriteString(",") } _, _ = p.out.WriteString("\n") } _, _ = p.out.WriteString(prefix) _, _ = p.out.WriteString(bunt.Style("]", bunt.Bold())) return nil } func (p *OutputProcessor) neatJSONofScalar(prefix string, obj interface{}) error { if obj == nil { _, _ = p.out.WriteString(p.colorize("null", "nullColor")) return nil } data, err := json.Marshal(obj) if err != nil { return err } color := p.determineColorByType(obj) _, _ = p.out.WriteString(prefix) parts := strings.Split(string(data), "\\n") for idx, part := range parts { _, _ = p.out.WriteString(p.colorize(part, color)) if idx < len(parts)-1 { _, _ = p.out.WriteString(p.colorize("\\n", "emptyStructures")) } } return nil } func cast(node yamlv3.Node) (interface{}, error) { if node.Kind != yamlv3.ScalarNode { return nil, fmt.Errorf("invalid node kind to cast, must be a scalar node") } switch node.Tag { case "!!str": return node.Value, nil case "!!timestamp": return parseTime(node.Value) case "!!int": return strconv.Atoi(node.Value) case "!!float": return strconv.ParseFloat(node.Value, 64) case "!!bool": return strconv.ParseBool(node.Value) case "!!null": return nil, nil default: return nil, fmt.Errorf("unknown tag %s", node.Tag) } } func parseTime(value string) (time.Time, error) { // YAML Spec regarding timestamp: https://yaml.org/type/timestamp.html var layouts = [...]string{ time.RFC3339, "2006-01-02T15:04:05.999999999Z", "2006-01-02t15:04:05.999999999-07:00", "2006-01-02 15:04:05.999999999 07:00", "2006-01-02 15:04:05.999999999", "2006-01-02", } for _, layout := range layouts { if result, err := time.Parse(layout, value); err == nil { return result, nil } } return time.Time{}, fmt.Errorf("value %q cannot be parsed as a timestamp", value) } neat-1.3.11/output_json_test.go000066400000000000000000000137361424540553100165200ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/bunt" . "github.com/gonvenience/neat" yamlv2 "gopkg.in/yaml.v2" yamlv3 "gopkg.in/yaml.v3" ) var _ = Describe("JSON output", func() { Context("create JSON output", func() { BeforeEach(func() { SetColorSettings(OFF, OFF) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) It("should create JSON output for a simple list", func() { result, err := ToJSONString([]interface{}{ "one", "two", "three", }) Expect(err).To(BeNil()) Expect(result).To(BeEquivalentTo(`["one", "two", "three"]`)) }) It("should create JSON output of nested maps", func() { example := yamlv2.MapSlice{ yamlv2.MapItem{ Key: "map", Value: yamlv2.MapSlice{ yamlv2.MapItem{ Key: "foo", Value: yamlv2.MapSlice{ yamlv2.MapItem{ Key: "bar", Value: yamlv2.MapSlice{ yamlv2.MapItem{ Key: "name", Value: "foobar", }, }, }, }, }, }, }, } var result string var err error var outputProcessor = NewOutputProcessor(false, false, &DefaultColorSchema) result, err = outputProcessor.ToCompactJSON(example) Expect(err).ToNot(HaveOccurred()) Expect(result).To(BeEquivalentTo(`{"map": {"foo": {"bar": {"name": "foobar"}}}}`)) result, err = outputProcessor.ToJSON(example) Expect(err).ToNot(HaveOccurred()) Expect(result).To(BeEquivalentTo(`{ "map": { "foo": { "bar": { "name": "foobar" } } } }`)) }) It("should create JSON output for empty structures", func() { example := yamlv2.MapSlice{ yamlv2.MapItem{ Key: "empty-map", Value: yamlv2.MapSlice{}, }, yamlv2.MapItem{ Key: "empty-list", Value: []interface{}{}, }, yamlv2.MapItem{ Key: "empty-scalar", Value: nil, }, } var result string var err error var outputProcessor = NewOutputProcessor(false, false, &DefaultColorSchema) result, err = outputProcessor.ToCompactJSON(example) Expect(err).ToNot(HaveOccurred()) Expect(result).To(BeEquivalentTo(`{"empty-map": {}, "empty-list": [], "empty-scalar": null}`)) result, err = outputProcessor.ToJSON(example) Expect(err).ToNot(HaveOccurred()) Expect(result).To(BeEquivalentTo(`{ "empty-map": {}, "empty-list": [], "empty-scalar": null }`)) }) It("should create compact JSON with correct quotes for different types", func() { result, err := ToJSONString([]interface{}{ "string", 42.0, 42, true, }) Expect(err).To(BeNil()) Expect(result).To(BeEquivalentTo(`["string", 42, 42, true]`)) }) It("should create JSON with correct quotes for different types", func() { result, err := NewOutputProcessor(true, true, &DefaultColorSchema).ToJSON([]interface{}{ "string", 42.0, 42, true, }) Expect(err).To(BeNil()) Expect(result).To(BeEquivalentTo(`[ "string", 42, 42, true ]`)) }) It("should not create JSON output with unquoted timestamps (https://github.com/gonvenience/neat/issues/69)", func() { example := func() *yamlv3.Node { var node yamlv3.Node if err := yamlv3.Unmarshal([]byte(`timestamp: 2021-08-21T00:00:00Z`), &node); err != nil { Fail(err.Error()) } return &node }() result, err := NewOutputProcessor(false, false, &DefaultColorSchema).ToCompactJSON(example) Expect(err).ToNot(HaveOccurred()) Expect(result).To(Equal(`{"timestamp": "2021-08-21T00:00:00Z"}`)) result, err = NewOutputProcessor(false, false, &DefaultColorSchema).ToJSON(example) Expect(err).ToNot(HaveOccurred()) Expect(result).To(Equal(`{ "timestamp": "2021-08-21T00:00:00Z" }`)) }) It("should parse all YAML spec conform timestamps", func() { var example yamlv3.Node Expect(yamlv3.Unmarshal([]byte(`timestamp: 2033-12-20`), &example)).To(BeNil()) result, err := NewOutputProcessor(false, false, &DefaultColorSchema).ToCompactJSON(example) Expect(err).ToNot(HaveOccurred()) Expect(result).To(Equal(`{"timestamp": "2033-12-20T00:00:00Z"}`)) result, err = NewOutputProcessor(false, false, &DefaultColorSchema).ToJSON(example) Expect(err).ToNot(HaveOccurred()) Expect(result).To(Equal(`{ "timestamp": "2033-12-20T00:00:00Z" }`)) }) }) Context("create JSON output with colors", func() { BeforeEach(func() { SetColorSettings(ON, ON) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) It("should create empty list output", func() { result, err := NewOutputProcessor(true, true, &DefaultColorSchema).ToJSON([]interface{}{}) Expect(err).To(BeNil()) Expect(result).To(BeEquivalentTo(Sprint("PaleGoldenrod{[]}"))) result, err = NewOutputProcessor(true, true, &DefaultColorSchema).ToJSON(yml("[]")) Expect(err).To(BeNil()) Expect(result).To(BeEquivalentTo(Sprint("PaleGoldenrod{[]}"))) }) }) }) neat-1.3.11/output_yaml.go000066400000000000000000000232431424540553100154440ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat import ( "fmt" "reflect" "strings" yamlv2 "gopkg.in/yaml.v2" yamlv3 "gopkg.in/yaml.v3" "github.com/gonvenience/bunt" ) // ToYAMLString marshals the provided object into YAML with text decorations // and is basically just a convenience function to create the output processor // and call its `ToYAML` function. func ToYAMLString(obj interface{}) (string, error) { return NewOutputProcessor(true, true, &DefaultColorSchema).ToYAML(obj) } // ToYAML processes the provided input object and tries to neatly output it as // human readable YAML honoring the preferences provided to the output processor func (p *OutputProcessor) ToYAML(obj interface{}) (string, error) { if err := p.neatYAML("", false, obj); err != nil { return "", err } p.out.Flush() return p.data.String(), nil } func (p *OutputProcessor) neatYAML(prefix string, skipIndentOnFirstLine bool, obj interface{}) error { switch t := obj.(type) { case yamlv2.MapSlice: return p.neatYAMLofMapSlice(prefix, skipIndentOnFirstLine, t) case []interface{}: return p.neatYAMLofSlice(prefix, skipIndentOnFirstLine, t) case []yamlv2.MapSlice: return p.neatYAMLofSlice(prefix, skipIndentOnFirstLine, p.simplify(t)) case yamlv3.Node: return p.neatYAMLofNode(prefix, skipIndentOnFirstLine, &t) default: switch reflect.TypeOf(obj).Kind() { case reflect.Ptr: return p.neatYAML(prefix, skipIndentOnFirstLine, reflect.ValueOf(obj).Elem().Interface()) case reflect.Struct: return p.neatYAMLOfStruct(prefix, skipIndentOnFirstLine, t) default: return p.neatYAMLofScalar(prefix, skipIndentOnFirstLine, t) } } } func (p *OutputProcessor) neatYAMLofMapSlice(prefix string, skipIndentOnFirstLine bool, mapslice yamlv2.MapSlice) error { for i, mapitem := range mapslice { if !skipIndentOnFirstLine || i > 0 { _, _ = p.out.WriteString(prefix) } keyString := fmt.Sprintf("%v:", mapitem.Key) if p.boldKeys { keyString = bunt.Style(keyString, bunt.Bold()) } _, _ = p.out.WriteString(p.colorize(keyString, "keyColor")) switch mapitem.Value.(type) { case yamlv2.MapSlice: if len(mapitem.Value.(yamlv2.MapSlice)) == 0 { _, _ = p.out.WriteString(" ") _, _ = p.out.WriteString(p.colorize("{}", "emptyStructures")) _, _ = p.out.WriteString("\n") } else { _, _ = p.out.WriteString("\n") if err := p.neatYAMLofMapSlice(prefix+p.prefixAdd(), false, mapitem.Value.(yamlv2.MapSlice)); err != nil { return err } } case []interface{}: if len(mapitem.Value.([]interface{})) == 0 { _, _ = p.out.WriteString(" ") _, _ = p.out.WriteString(p.colorize("[]", "emptyStructures")) _, _ = p.out.WriteString("\n") } else { _, _ = p.out.WriteString("\n") if err := p.neatYAMLofSlice(prefix, false, mapitem.Value.([]interface{})); err != nil { return err } } default: _, _ = p.out.WriteString(" ") if err := p.neatYAMLofScalar(prefix, false, mapitem.Value); err != nil { return err } } } return nil } func (p *OutputProcessor) neatYAMLofSlice(prefix string, skipIndentOnFirstLine bool, list []interface{}) error { for _, entry := range list { _, _ = p.out.WriteString(prefix) _, _ = p.out.WriteString(p.colorize("-", "dashColor")) _, _ = p.out.WriteString(" ") if err := p.neatYAML(prefix+p.prefixAdd(), true, entry); err != nil { return err } } return nil } func (p *OutputProcessor) neatYAMLofScalar(prefix string, skipIndentOnFirstLine bool, obj interface{}) error { // Process nil values immediately and return afterwards if obj == nil { _, _ = p.out.WriteString(p.colorize("null", "nullColor")) _, _ = p.out.WriteString("\n") return nil } // Any other value: Run through Go YAML marshaller and colorize afterwards data, err := yamlv2.Marshal(obj) if err != nil { return err } // Decide on one color to be used color := p.determineColorByType(obj) // Cast byte slice to string, remove trailing newlines, split into lines for i, line := range strings.Split(strings.TrimSpace(string(data)), "\n") { if i > 0 { _, _ = p.out.WriteString(prefix) } _, _ = p.out.WriteString(p.colorize(line, color)) _, _ = p.out.WriteString("\n") } return nil } func (p *OutputProcessor) neatYAMLofNode(prefix string, skipIndentOnFirstLine bool, node *yamlv3.Node) error { keyStyles := []bunt.StyleOption{} if p.boldKeys { keyStyles = append(keyStyles, bunt.Bold()) } switch node.Kind { case yamlv3.DocumentNode: bunt.Fprint(p.out, p.colorize("---", "documentStart"), "\n") for _, content := range node.Content { if err := p.neatYAML(prefix, false, content); err != nil { return err } } if len(node.FootComment) > 0 { fmt.Fprint(p.out, p.colorize(node.FootComment, "commentColor"), "\n") } case yamlv3.SequenceNode: for i, entry := range node.Content { if i == 0 { if !skipIndentOnFirstLine { fmt.Fprint(p.out, prefix) } } else { fmt.Fprint(p.out, prefix) } fmt.Fprint(p.out, p.colorize("-", "dashColor"), " ") if err := p.neatYAMLofNode(prefix+p.prefixAdd(), true, entry); err != nil { return err } } case yamlv3.MappingNode: for i := 0; i < len(node.Content); i += 2 { if !skipIndentOnFirstLine || i > 0 { fmt.Fprint(p.out, prefix) } key := node.Content[i] if len(key.HeadComment) > 0 { fmt.Fprint(p.out, p.colorize(key.HeadComment, "commentColor"), "\n") } fmt.Fprint(p.out, bunt.Style(p.colorize(fmt.Sprintf("%s:", key.Value), "keyColor"), keyStyles...), ) value := node.Content[i+1] switch value.Kind { case yamlv3.MappingNode: if len(value.Content) == 0 { fmt.Fprint(p.out, p.createAnchorDefinition(value), " ", p.colorize("{}", "emptyStructures"), "\n") } else { fmt.Fprint(p.out, p.createAnchorDefinition(value), "\n") if err := p.neatYAMLofNode(prefix+p.prefixAdd(), false, value); err != nil { return err } } case yamlv3.SequenceNode: if len(value.Content) == 0 { fmt.Fprint(p.out, p.createAnchorDefinition(value), " ", p.colorize("[]", "emptyStructures"), "\n") } else { fmt.Fprint(p.out, p.createAnchorDefinition(value), "\n") if err := p.neatYAMLofNode(prefix, false, value); err != nil { return err } } case yamlv3.ScalarNode: fmt.Fprint(p.out, p.createAnchorDefinition(value), " ") if err := p.neatYAMLofNode(prefix+p.prefixAdd(), false, value); err != nil { return err } case yamlv3.AliasNode: fmt.Fprintf(p.out, " %s\n", p.colorize("*"+value.Value, "anchorColor")) } if len(key.FootComment) > 0 { fmt.Fprint(p.out, p.colorize(key.FootComment, "commentColor"), "\n") } } case yamlv3.ScalarNode: var colorName = "scalarDefaultColor" switch node.Tag { case "!!binary": colorName = "binaryColor" case "!!str": colorName = "scalarDefaultColor" case "!!float": colorName = "floatColor" case "!!int": colorName = "intColor" case "!!bool": colorName = "boolColor" } if node.Value == "nil" { colorName = "nullColor" } lines := strings.Split(node.Value, "\n") switch len(lines) { case 1: if strings.ContainsAny(node.Value, " *&:,") { fmt.Fprint(p.out, p.colorizef(colorName, `"%s"`, node.Value)) } else { fmt.Fprint(p.out, p.colorizef(colorName, node.Value)) } default: colorName = "multiLineTextColor" fmt.Fprint(p.out, p.colorize("|", colorName), "\n") for i, line := range lines { fmt.Fprint(p.out, prefix, p.colorize(line, colorName), ) if i != len(lines)-1 { fmt.Fprint(p.out, "\n") } } } if len(node.LineComment) > 0 { fmt.Fprint(p.out, " ", p.colorize(node.LineComment, "commentColor")) } fmt.Fprint(p.out, "\n") if len(node.FootComment) > 0 { fmt.Fprint(p.out, p.colorize(node.FootComment, "commentColor"), "\n") } case yamlv3.AliasNode: if err := p.neatYAMLofNode(prefix, skipIndentOnFirstLine, node.Alias); err != nil { return err } } return nil } func (p *OutputProcessor) neatYAMLOfStruct(prefix string, skipIndentOnFirstLine bool, obj interface{}) error { // There might be better ways to do it. With generic struct objects, the // only option is to do a roundtrip marshal and unmarshal to get the // object into a universal Go YAML library version 3 node object and // to render the node instead. data, err := yamlv3.Marshal(obj) if err != nil { return err } var tmp yamlv3.Node if err := yamlv3.Unmarshal(data, &tmp); err != nil { return err } return p.neatYAML(prefix, skipIndentOnFirstLine, tmp) } func (p *OutputProcessor) createAnchorDefinition(node *yamlv3.Node) string { if len(node.Anchor) != 0 { return fmt.Sprint(" ", p.colorize("&"+node.Anchor, "anchorColor")) } return "" } neat-1.3.11/output_yaml_test.go000066400000000000000000000220341424540553100165000ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/bunt" . "github.com/gonvenience/neat" yamlv2 "gopkg.in/yaml.v2" yamlv3 "gopkg.in/yaml.v3" ) var _ = Describe("YAML output", func() { BeforeEach(func() { SetColorSettings(OFF, OFF) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) Context("process input JSON for YAML output", func() { It("should convert JSON to YAML", func() { var content yamlv2.MapSlice if err := yamlv2.Unmarshal([]byte(`{ "name": "foobar", "list": [A, B, C] }`), &content); err != nil { Fail(err.Error()) } result, err := ToYAMLString(content) Expect(err).To(BeNil()) Expect(result).To(BeEquivalentTo(`name: foobar list: - A - B - C `)) }) It("should preserve the order inside the structure", func() { var content yamlv2.MapSlice err := yamlv2.Unmarshal([]byte(`{ "list": [C, B, A], "name": "foobar" }`), &content) if err != nil { Fail(err.Error()) } result, err := ToYAMLString(content) Expect(err).To(BeNil()) Expect(result).To(Equal(`list: - C - B - A name: foobar `)) }) }) Context("create YAML output (go-yaml v2)", func() { It("should create YAML output for a simple list", func() { result, err := ToYAMLString([]interface{}{ "one", "two", "three", }) Expect(err).To(BeNil()) Expect(result).To(BeEquivalentTo(`- one - two - three `)) }) It("should create YAML output for a specific YAML v2 MapSlice list", func() { result, err := ToYAMLString([]yamlv2.MapSlice{ { yamlv2.MapItem{ Key: "name", Value: "one", }, }, { yamlv2.MapItem{ Key: "name", Value: "two", }, }, }) Expect(err).To(BeNil()) Expect(result).To(BeEquivalentTo(`- name: one - name: two `)) }) It("should create YAML output of nested maps", func() { result, err := ToYAMLString(yamlv2.MapSlice{ yamlv2.MapItem{ Key: "map", Value: yamlv2.MapSlice{ yamlv2.MapItem{ Key: "foo", Value: yamlv2.MapSlice{ yamlv2.MapItem{ Key: "bar", Value: yamlv2.MapSlice{ yamlv2.MapItem{ Key: "name", Value: "foobar", }, }, }, }, }, }, }, }) Expect(err).To(BeNil()) Expect(result).To(BeEquivalentTo(`map: foo: bar: name: foobar `)) }) It("should create YAML output for empty structures", func() { result, err := ToYAMLString(yamlv2.MapSlice{ yamlv2.MapItem{ Key: "empty-map", Value: yamlv2.MapSlice{}, }, yamlv2.MapItem{ Key: "empty-list", Value: []interface{}{}, }, yamlv2.MapItem{ Key: "empty-scalar", Value: nil, }, }) Expect(err).To(BeNil()) Expect(result).To(BeEquivalentTo(`empty-map: {} empty-list: [] empty-scalar: null `)) }) }) Context("create YAML output (go-yaml v3)", func() { It("should create YAML output based on YAML node structure", func() { example := []byte(`--- # start of document # before map map: # at map definition key: value # value # before scalars scalars: # at scalar definition boolean: true # true number: 42 # 42 float: 47.11 string: foobar data: !!binary Zm9vYmFyCg== # before list list: # at list definition - one # one - two # two # before multiline multiline: |- This is a multi line te xt. # before zeros zeros: map: {} list: [] scalar: nil # before anchors anchors: scalar: &scalaranchor 42 same-scalar: *scalaranchor list: &listanchor - one - two same-list: *listanchor empty-list: &emptylist [] same-empty-list: *emptylist map: &mapanchor key: value same-map: *mapanchor empty-map: &emptymap {} same-empty-map: *emptymap # end of document `) expected := `--- # start of document # before map map: key: value # value # before scalars scalars: boolean: true # true number: 42 # 42 float: 47.11 string: foobar data: Zm9vYmFyCg== # before list list: - one # one - two # two # before multiline multiline: | This is a multi line te xt. # before zeros zeros: map: {} list: [] scalar: nil # before anchors anchors: scalar: &scalaranchor 42 same-scalar: *scalaranchor list: &listanchor - one - two same-list: *listanchor empty-list: &emptylist [] same-empty-list: *emptylist map: &mapanchor key: value same-map: *mapanchor empty-map: &emptymap {} same-empty-map: *emptymap # end of document ` var node yamlv3.Node Expect(yamlv3.Unmarshal(example, &node)).ToNot(HaveOccurred()) output, err := ToYAMLString(node) Expect(err).ToNot(HaveOccurred()) Expect(output).To(BeEquivalentTo(expected)) }) It("should create YAML output of so called named entry lists", func() { example := []byte(`--- yaml: named-entry-list-using-name: - name: A - name: B - name: C - name: X - name: Z named-entry-list-using-key: - key: A - key: B - key: C - key: X - key: Z named-entry-list-using-id: - id: A - id: B - id: C - id: X - id: Z `) expected := `--- yaml: named-entry-list-using-name: - name: A - name: B - name: C - name: X - name: Z named-entry-list-using-key: - key: A - key: B - key: C - key: X - key: Z named-entry-list-using-id: - id: A - id: B - id: C - id: X - id: Z ` var node yamlv3.Node Expect(yamlv3.Unmarshal(example, &node)).ToNot(HaveOccurred()) output, err := ToYAMLString(node) Expect(err).ToNot(HaveOccurred()) Expect(output).To(BeEquivalentTo(expected)) }) It("should create YAML output of multi-line text", func() { example := []byte(`--- data: repos.yaml: |- repos: - apply_requirements: - approved - mergeable id: /.*/ test: /.*/ `) expected := `--- data: repos.yaml: | repos: - apply_requirements: - approved - mergeable id: /.*/ test: /.*/ ` var node yamlv3.Node Expect(yamlv3.Unmarshal(example, &node)).ToNot(HaveOccurred()) output, err := ToYAMLString(node) Expect(err).ToNot(HaveOccurred()) Expect(output).To(BeEquivalentTo(expected)) }) }) Context("create YAML output for type struct", func() { type Dependency struct { Name string `yaml:"name"` Version string `yaml:"version"` Active bool `yaml:"active"` } type Example struct { Name string `yaml:"name"` Version string `yaml:"version"` Dependencies []Dependency `yaml:"dependencies"` } var yml = func(input string) yamlv3.Node { var result yamlv3.Node if err := yamlv3.Unmarshal([]byte(input), &result); err != nil { Fail(err.Error()) } return result } It("should output a generic type struct", func() { SetColorSettings(ON, ON) defer SetColorSettings(AUTO, AUTO) expected, _ := ToYAMLString(yml(`--- name: foobar version: v1.0.0 dependencies: - name: foo version: v0.5.0 active: true - name: bar version: v0.5.0 active: true `)) output, err := ToYAMLString(Example{ Name: "foobar", Version: "v1.0.0", Dependencies: []Dependency{ { Name: "foo", Version: "v0.5.0", Active: true, }, { Name: "bar", Version: "v0.5.0", Active: true, }, }, }) Expect(err).ToNot(HaveOccurred()) Expect(output).To(Equal(expected)) }) It("should output a pointer to a generic type struct", func() { SetColorSettings(ON, ON) defer SetColorSettings(AUTO, AUTO) expected, _ := ToYAMLString(yml(`--- name: foobar version: v1.0.0 dependencies: - name: foo version: v0.5.0 active: true - name: bar version: v0.5.0 active: true `)) output, err := ToYAMLString(&Example{ Name: "foobar", Version: "v1.0.0", Dependencies: []Dependency{ { Name: "foo", Version: "v0.5.0", Active: true, }, { Name: "bar", Version: "v0.5.0", Active: true, }, }, }) Expect(err).ToNot(HaveOccurred()) Expect(output).To(Equal(expected)) }) }) }) neat-1.3.11/table.go000066400000000000000000000144351424540553100141540ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat import ( "bytes" "strings" "github.com/gonvenience/bunt" ) // TableOption defines options/settings for tables. type TableOption func(*options) // Alignment defines the text alignment option for a table cell. type Alignment int // Table cells support three types of alignment: left, right, center. const ( Left Alignment = iota Right Center ) type options struct { filler string separator string desiredRowWidth int columnAlignment []Alignment errors []error omitLinefeedAtEnd bool rowLimit int } func defaultOptions(cols int) options { alignments := make([]Alignment, cols) for i := 0; i < cols; i++ { alignments[i] = Left } return options{ filler: " ", separator: " ", desiredRowWidth: -1, columnAlignment: alignments, errors: []error{}, omitLinefeedAtEnd: false, rowLimit: -1, } } // VertialBarSeparator sets a solid veritcal bar as the column separator. func VertialBarSeparator() TableOption { return func(opts *options) { opts.separator = " │ " } } // CustomSeparator set a custom separator string (other than the default single space) func CustomSeparator(separator string) TableOption { return func(opts *options) { opts.separator = separator } } // DesiredWidth sets the desired width of the table func DesiredWidth(width int) TableOption { return func(opts *options) { opts.desiredRowWidth = width } } // AlignRight sets alignment to right for the given columns (referenced by index) func AlignRight(cols ...int) TableOption { return func(opts *options) { for _, col := range cols { if col < 0 || col >= len(opts.columnAlignment) { opts.errors = append(opts.errors, &ColumnIndexIsOutOfBoundsError{col}) } else { opts.columnAlignment[col] = Right } } } } // AlignCenter sets alignment to center for the given columns (referenced by index) func AlignCenter(cols ...int) TableOption { return func(opts *options) { for _, col := range cols { if col < 0 || col >= len(opts.columnAlignment) { opts.errors = append(opts.errors, &ColumnIndexIsOutOfBoundsError{col}) } else { opts.columnAlignment[col] = Center } } } } // OmitLinefeedAtTableEnd tells the table renderer to not add a final linefeed func OmitLinefeedAtTableEnd() TableOption { return func(opts *options) { opts.omitLinefeedAtEnd = true } } // LimitRows sets a limit at which point the table is truncated func LimitRows(limit int) TableOption { return func(opts *options) { opts.rowLimit = limit } } // Table renders a string with a well spaced and aligned table output func Table(table [][]string, tableOptions ...TableOption) (string, error) { maxs, err := lookupMaxLengthPerColumn(table) if err != nil { return "", err } cols := len(maxs) options := defaultOptions(cols) for _, userOption := range tableOptions { userOption(&options) } if len(options.errors) > 0 { return "", options.errors[0] } var ( buf bytes.Buffer idx = 0 rowLimit = len(table) ) if (options.rowLimit >= 0) && (options.rowLimit < len(table)) { rowLimit = options.rowLimit } for ; idx < rowLimit; idx++ { row := table[idx] if options.desiredRowWidth > 0 { rawRowWidth := lookupPlainRowLength(row, maxs, options.separator) if rawRowWidth > options.desiredRowWidth { return "", &RowLengthExceedsDesiredWidthError{} } for y := range row { maxs[y] += (options.desiredRowWidth - rawRowWidth) / cols } } for y, cell := range row { notLastCol := y < len(row)-1 fillment := strings.Repeat( options.filler, maxs[y]-bunt.PlainTextLength(cell), ) switch options.columnAlignment[y] { case Left: buf.WriteString(cell) if notLastCol { buf.WriteString(fillment) } case Right: buf.WriteString(fillment) buf.WriteString(cell) case Center: x := bunt.PlainTextLength(fillment) / 2 buf.WriteString(fillment[:x]) buf.WriteString(cell) if notLastCol { buf.WriteString(fillment[x:]) } } if notLastCol { buf.WriteString(options.separator) } } // Make sure to add a linefeed to the end of each line, unless it is // the last line of the table and the settings indicate that there must // be no linefeed at the last line if lastline := idx >= rowLimit-1; !lastline || !options.omitLinefeedAtEnd { // Special case in which the number of table rows is limited, add an // ellipsis to indicate the truncation if lastline && rowLimit >= 0 && rowLimit < len(table) { buf.WriteString("\n[...]") } buf.WriteString("\n") } } return buf.String(), nil } func lookupMaxLengthPerColumn(table [][]string) ([]int, error) { if len(table) == 0 { return nil, &EmptyTableError{} } cols := len(table[0]) for _, row := range table { if len(row) != cols { return nil, &ImbalancedTableError{} } } maxs := make([]int, cols) for _, row := range table { for y, cell := range row { if max := bunt.PlainTextLength(cell); max > maxs[y] { maxs[y] = max } } } return maxs, nil } func lookupPlainRowLength(row []string, maxs []int, separator string) int { var length int for i := range row { length += maxs[i] + bunt.PlainTextLength(separator) } return length } neat-1.3.11/table_error.go000066400000000000000000000044011424540553100153550ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat import "fmt" // EmptyTableError is used to describe that the input table was either nil, or empty. type EmptyTableError struct { } func (e *EmptyTableError) Error() string { return "unable to render table, the input table is empty" } // ImbalancedTableError is used to describe that not all rows have the same number of columns type ImbalancedTableError struct { } func (e *ImbalancedTableError) Error() string { return "unable to render table, some rows have more or less columns than other rows" } // RowLengthExceedsDesiredWidthError is used to describe that the table cannot be rendered, because at least one row exceeds the desired width type RowLengthExceedsDesiredWidthError struct { } func (e *RowLengthExceedsDesiredWidthError) Error() string { return "unable to render table, because at least one row exceeds the desired width" } // ColumnIndexIsOutOfBoundsError is used to describe that a provided column index is out of bounds type ColumnIndexIsOutOfBoundsError struct { ColumnIdx int } func (e *ColumnIndexIsOutOfBoundsError) Error() string { return fmt.Sprintf("unable to render table, the provided column index %d is out of bounds", e.ColumnIdx) } neat-1.3.11/table_test.go000066400000000000000000000146301424540553100152100ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package neat_test import ( "fmt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/neat" ) var _ = Describe("Table formatting", func() { Context("Process two-dimensional slices as tables to be printed", func() { It("should work for simplest of tables with no additional formatting", func() { input := [][]string{ {"eins", "zwei", "drei"}, {"one", "two", "three"}, {"un", "deux", "trois"}, } expectedResult := `eins zwei drei one two three un deux trois ` tableString, err := Table(input) Expect(err).ToNot(HaveOccurred()) Expect(tableString).To(BeEquivalentTo(expectedResult)) }) It("should work with additional formatting for inner borders", func() { input := [][]string{ {"eins", "zwei", "drei"}, {"one", "two", "three"}, {"un", "deux", "trois"}, } expectedResult := `eins │ zwei │ drei one │ two │ three un │ deux │ trois ` tableString, err := Table(input, VertialBarSeparator()) Expect(err).ToNot(HaveOccurred()) Expect(tableString).To(BeEquivalentTo(expectedResult)) }) It("should work with a custom separator string", func() { input := [][]string{ {"eins", "zwei", "drei"}, {"one", "two", "three"}, {"un", "deux", "trois"}, } expectedResult := `eins / zwei / drei one / two / three un / deux / trois ` tableString, err := Table(input, CustomSeparator(" / ")) Expect(err).ToNot(HaveOccurred()) Expect(tableString).To(BeEquivalentTo(expectedResult)) }) It("should work with additional formatting for row padding", func() { input := [][]string{ {"eins", "zwei", "drei"}, {"one", "two", "three"}, {"un", "deux", "trois"}, } expectedResult := `eins zwei drei one two three un deux trois ` tableString, err := Table(input, DesiredWidth(80)) Expect(err).ToNot(HaveOccurred()) Expect(tableString).To(BeEquivalentTo(expectedResult)) }) It("should work with additional formatting for alignment", func() { input := [][]string{ {"eins", "zwei", "drei", "vier", "fünf"}, {"one", "two", "three", "four", "five"}, {"un", "deux", "trois", "quatre", "cinq"}, {"uno", "dos", "tres", "cuatro", "cinco"}, } expectedResult := `eins zwei drei vier fünf one two three four five un deux trois quatre cinq uno dos tres cuatro cinco ` tableString, err := Table(input, AlignRight(1, 4), AlignCenter(3)) Expect(err).ToNot(HaveOccurred()) Expect(fmt.Sprintf("%#v", tableString)).To(BeEquivalentTo(fmt.Sprintf("%#v", expectedResult))) }) It("should error if a row would exceed the desired table width", func() { input := [][]string{ {"#1", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum."}, {"#2", "Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."}, } tableString, err := Table(input, DesiredWidth(120)) Expect(err).Should(MatchError(&RowLengthExceedsDesiredWidthError{})) Expect(tableString).To(BeEquivalentTo("")) }) It("should error if empty input is provided", func() { tableString, err := Table(nil) Expect(err).Should(MatchError(&EmptyTableError{})) Expect(tableString).To(BeEquivalentTo("")) }) It("should error if imbalanced table is provided", func() { tableString, err := Table([][]string{ {"eins", "zwei", "drei", "vier", "fünf"}, {"one", "two", "three", "four"}, }) Expect(err).Should(MatchError(&ImbalancedTableError{})) Expect(tableString).To(BeEquivalentTo("")) }) It("should error if a column index based table option is out of bounds", func() { tableString, err := Table([][]string{ {"eins", "zwei", "drei", "vier"}, {"one", "two", "three", "four"}, }, AlignCenter(4)) Expect(err).Should(MatchError(&ColumnIndexIsOutOfBoundsError{4})) Expect(tableString).To(BeEquivalentTo("")) }) It("should be possible to create a table without a final linefeed", func() { input := [][]string{ {"eins", "zwei", "drei"}, {"one", "two", "three"}, {"un", "deux", "trois"}, } expectedResult := `eins zwei drei one two three un deux trois` tableString, err := Table(input, OmitLinefeedAtTableEnd()) Expect(err).ToNot(HaveOccurred()) Expect(tableString).To(BeEquivalentTo(expectedResult)) }) It("should be possible to limit the number of rows", func() { input := [][]string{ {"eins", "zwei", "drei"}, {"one", "two", "three"}, {"un", "deux", "trois"}, } expectedResult := `eins zwei drei one two three [...] ` tableString, err := Table(input, LimitRows(2)) Expect(err).ToNot(HaveOccurred()) Expect(tableString).To(BeEquivalentTo(expectedResult)) }) It("should not fail when using a row limit which is greater than the table length", func() { input := [][]string{ {"eins", "zwei", "drei"}, {"one", "two", "three"}, {"un", "deux", "trois"}, } expectedResult := `eins zwei drei one two three un deux trois ` tableString, err := Table(input, LimitRows(25)) Expect(err).ToNot(HaveOccurred()) Expect(tableString).To(BeEquivalentTo(expectedResult)) }) }) })