diff -Nru golang-github-ryanuber-columnize-2.1.0/columnize.go golang-github-ryanuber-columnize-2.1.1/columnize.go --- golang-github-ryanuber-columnize-2.1.0/columnize.go 2015-09-15 23:29:42.000000000 +0000 +++ golang-github-ryanuber-columnize-2.1.1/columnize.go 2019-03-19 23:35:15.000000000 +0000 @@ -1,10 +1,13 @@ package columnize import ( + "bytes" "fmt" "strings" ) +// Config can be used to tune certain parameters which affect the way +// in which Columnize will format output text. type Config struct { // The string by which the lines of input will be split. Delim string @@ -15,80 +18,37 @@ // The string by which columns of output will be prefixed. Prefix string - // A replacement string to replace empty fields + // A replacement string to replace empty fields. Empty string + + // NoTrim disables automatic trimming of inputs. + NoTrim bool } -// Returns a Config with default values. +// DefaultConfig returns a *Config with default values. func DefaultConfig() *Config { return &Config{ Delim: "|", Glue: " ", Prefix: "", + Empty: "", + NoTrim: false, } } -// Returns a list of elements, each representing a single item which will -// belong to a column of output. -func getElementsFromLine(config *Config, line string) []interface{} { - elements := make([]interface{}, 0) - for _, field := range strings.Split(line, config.Delim) { - value := strings.TrimSpace(field) - if value == "" && config.Empty != "" { - value = config.Empty - } - elements = append(elements, value) - } - return elements -} - -// Examines a list of strings and determines how wide each column should be -// considering all of the elements that need to be printed within it. -func getWidthsFromLines(config *Config, lines []string) []int { - var widths []int - - for _, line := range lines { - elems := getElementsFromLine(config, line) - for i := 0; i < len(elems); i++ { - l := len(elems[i].(string)) - if len(widths) <= i { - widths = append(widths, l) - } else if widths[i] < l { - widths[i] = l - } - } - } - return widths -} - -// Given a set of column widths and the number of columns in the current line, -// returns a sprintf-style format string which can be used to print output -// aligned properly with other lines using the same widths set. -func (c *Config) getStringFormat(widths []int, columns int) string { - // Start with the prefix, if any was given. - stringfmt := c.Prefix - - // Create the format string from the discovered widths - for i := 0; i < columns && i < len(widths); i++ { - if i == columns-1 { - stringfmt += "%s\n" - } else { - stringfmt += fmt.Sprintf("%%-%ds%s", widths[i], c.Glue) - } - } - return stringfmt -} - // MergeConfig merges two config objects together and returns the resulting // configuration. Values from the right take precedence over the left side. func MergeConfig(a, b *Config) *Config { - var result Config = *a - // Return quickly if either side was nil - if a == nil || b == nil { - return &result + if a == nil { + return b + } + if b == nil { + return a } + var result Config = *a + if b.Delim != "" { result.Delim = b.Delim } @@ -101,25 +61,122 @@ if b.Empty != "" { result.Empty = b.Empty } + if b.NoTrim { + result.NoTrim = true + } return &result } -// Format is the public-facing interface that takes either a plain string -// or a list of strings and returns nicely aligned output. -func Format(lines []string, config *Config) string { - var result string +// stringFormat, given a set of column widths and the number of columns in +// the current line, returns a sprintf-style format string which can be used +// to print output aligned properly with other lines using the same widths set. +func stringFormat(c *Config, widths []int, columns int) string { + // Create the buffer with an estimate of the length + buf := bytes.NewBuffer(make([]byte, 0, (6+len(c.Glue))*columns)) + + // Start with the prefix, if any was given. The buffer will not return an + // error so it does not need to be handled + buf.WriteString(c.Prefix) + + // Create the format string from the discovered widths + for i := 0; i < columns && i < len(widths); i++ { + if i == columns-1 { + buf.WriteString("%s\n") + } else { + fmt.Fprintf(buf, "%%-%ds%s", widths[i], c.Glue) + } + } + return buf.String() +} + +// elementsFromLine returns a list of elements, each representing a single +// item which will belong to a column of output. +func elementsFromLine(config *Config, line string) []interface{} { + separated := strings.Split(line, config.Delim) + elements := make([]interface{}, len(separated)) + for i, field := range separated { + value := field + if !config.NoTrim { + value = strings.TrimSpace(field) + } + + // Apply the empty value, if configured. + if value == "" && config.Empty != "" { + value = config.Empty + } + elements[i] = value + } + return elements +} +// runeLen calculates the number of visible "characters" in a string +func runeLen(s string) int { + l := 0 + for _ = range s { + l++ + } + return l +} + +// widthsFromLines examines a list of strings and determines how wide each +// column should be considering all of the elements that need to be printed +// within it. +func widthsFromLines(config *Config, lines []string) []int { + widths := make([]int, 0, 8) + + for _, line := range lines { + elems := elementsFromLine(config, line) + for i := 0; i < len(elems); i++ { + l := runeLen(elems[i].(string)) + if len(widths) <= i { + widths = append(widths, l) + } else if widths[i] < l { + widths[i] = l + } + } + } + return widths +} + +// Format is the public-facing interface that takes a list of strings and +// returns nicely aligned column-formatted text. +func Format(lines []string, config *Config) string { conf := MergeConfig(DefaultConfig(), config) - widths := getWidthsFromLines(conf, lines) + widths := widthsFromLines(conf, lines) + + // Estimate the buffer size + glueSize := len(conf.Glue) + var size int + for _, w := range widths { + size += w + glueSize + } + size *= len(lines) + + // Create the buffer + buf := bytes.NewBuffer(make([]byte, 0, size)) + + // Create a cache for the string formats + fmtCache := make(map[int]string, 16) // Create the formatted output using the format string for _, line := range lines { - elems := getElementsFromLine(conf, line) - stringfmt := conf.getStringFormat(widths, len(elems)) - result += fmt.Sprintf(stringfmt, elems...) + elems := elementsFromLine(conf, line) + + // Get the string format using cache + numElems := len(elems) + stringfmt, ok := fmtCache[numElems] + if !ok { + stringfmt = stringFormat(conf, widths, numElems) + fmtCache[numElems] = stringfmt + } + + fmt.Fprintf(buf, stringfmt, elems...) } + // Get the string result + result := buf.String() + // Remove trailing newline without removing leading/trailing space if n := len(result); n > 0 && result[n-1] == '\n' { result = result[:n-1] @@ -128,7 +185,7 @@ return result } -// Convenience function for using Columnize as easy as possible. +// SimpleFormat is a convenience function to format text with the defaults. func SimpleFormat(lines []string) string { return Format(lines, nil) } diff -Nru golang-github-ryanuber-columnize-2.1.0/columnize_test.go golang-github-ryanuber-columnize-2.1.1/columnize_test.go --- golang-github-ryanuber-columnize-2.1.0/columnize_test.go 2015-09-15 23:29:42.000000000 +0000 +++ golang-github-ryanuber-columnize-2.1.1/columnize_test.go 2019-03-19 23:35:15.000000000 +0000 @@ -1,6 +1,12 @@ package columnize -import "testing" +import ( + "fmt" + "reflect" + "testing" + + crand "crypto/rand" +) func TestListOfStringsInput(t *testing.T) { input := []string{ @@ -70,7 +76,66 @@ expected += "short short short" if output != expected { - t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + printableProof := fmt.Sprintf("\nGot: %+q", output) + printableProof += fmt.Sprintf("\nExpected: %+q", expected) + t.Fatalf("\n%s", printableProof) + } +} + +func TestColumnWidthCalculatorNonASCII(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "⌘⌘⌘⌘⌘⌘⌘⌘ | Longer than B | Longer than C", + "short | short | short", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "⌘⌘⌘⌘⌘⌘⌘⌘ Longer than B Longer than C\n" + expected += "short short short" + + if output != expected { + printableProof := fmt.Sprintf("\nGot: %+q", output) + printableProof += fmt.Sprintf("\nExpected: %+q", expected) + t.Fatalf("\n%s", printableProof) + } +} + +func BenchmarkColumnWidthCalculator(b *testing.B) { + // Generate the input + input := []string{ + "UUID A | UUID B | UUID C | Column D | Column E", + } + + format := "%s|%s|%s|%s" + short := "short" + + uuid := func() string { + buf := make([]byte, 16) + if _, err := crand.Read(buf); err != nil { + panic(fmt.Errorf("failed to read random bytes: %v", err)) + } + + return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x", + buf[0:4], + buf[4:6], + buf[6:8], + buf[8:10], + buf[10:16]) + } + + for i := 0; i < 1000; i++ { + l := fmt.Sprintf(format, uuid()[:8], uuid()[:12], uuid(), short, short) + input = append(input, l) + } + + config := DefaultConfig() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + Format(input, config) } } @@ -91,6 +156,24 @@ } } +func TestVariedInputSpacing_NoTrim(t *testing.T) { + input := []string{ + "Column A|Column B|Column C", + "x|y| z", + } + + config := DefaultConfig() + config.NoTrim = true + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + func TestUnmatchedColumnCounts(t *testing.T) { input := []string{ "Column A | Column B | Column C", @@ -216,27 +299,42 @@ } func TestMergeConfig(t *testing.T) { - conf1 := &Config{Delim: "a", Glue: "a", Prefix: "a", Empty: "a"} - conf2 := &Config{Delim: "b", Glue: "b", Prefix: "b", Empty: "b"} - conf3 := &Config{Delim: "c", Prefix: "c"} - - m := MergeConfig(conf1, conf2) - if m.Delim != "b" || m.Glue != "b" || m.Prefix != "b" || m.Empty != "b" { - t.Fatalf("bad: %#v", m) - } - - m = MergeConfig(conf1, conf3) - if m.Delim != "c" || m.Glue != "a" || m.Prefix != "c" || m.Empty != "a" { - t.Fatalf("bad: %#v", m) - } - - m = MergeConfig(conf1, nil) - if m.Delim != "a" || m.Glue != "a" || m.Prefix != "a" || m.Empty != "a" { - t.Fatalf("bad: %#v", m) - } - - m = MergeConfig(conf1, &Config{}) - if m.Delim != "a" || m.Glue != "a" || m.Prefix != "a" || m.Empty != "a" { - t.Fatalf("bad: %#v", m) + for _, tc := range []struct { + desc string + configA *Config + configB *Config + expect *Config + }{ + { + "merges b over a", + &Config{Delim: "a", Glue: "a", Prefix: "a", Empty: "a"}, + &Config{Delim: "b", Glue: "b", Prefix: "b", Empty: "b"}, + &Config{Delim: "b", Glue: "b", Prefix: "b", Empty: "b"}, + }, + { + "merges only non-empty config values", + &Config{Delim: "a", Glue: "a", Prefix: "a", Empty: "a"}, + &Config{Delim: "b", Prefix: "b"}, + &Config{Delim: "b", Glue: "a", Prefix: "b", Empty: "a"}, + }, + { + "takes b if a is nil", + nil, + &Config{Delim: "b", Glue: "b", Prefix: "b", Empty: "b"}, + &Config{Delim: "b", Glue: "b", Prefix: "b", Empty: "b"}, + }, + { + "takes a if b is nil", + &Config{Delim: "a", Glue: "a", Prefix: "a", Empty: "a"}, + nil, + &Config{Delim: "a", Glue: "a", Prefix: "a", Empty: "a"}, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + m := MergeConfig(tc.configA, tc.configB) + if !reflect.DeepEqual(m, tc.expect) { + t.Fatalf("\nexpect:\n%#v\n\nactual:\n%#v", tc.expect, m) + } + }) } } diff -Nru golang-github-ryanuber-columnize-2.1.0/COPYING golang-github-ryanuber-columnize-2.1.1/COPYING --- golang-github-ryanuber-columnize-2.1.0/COPYING 2015-09-15 23:29:42.000000000 +0000 +++ golang-github-ryanuber-columnize-2.1.1/COPYING 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -MIT LICENSE - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff -Nru golang-github-ryanuber-columnize-2.1.0/debian/changelog golang-github-ryanuber-columnize-2.1.1/debian/changelog --- golang-github-ryanuber-columnize-2.1.0/debian/changelog 2018-04-18 06:19:43.000000000 +0000 +++ golang-github-ryanuber-columnize-2.1.1/debian/changelog 2019-09-25 14:43:13.000000000 +0000 @@ -1,3 +1,13 @@ +golang-github-ryanuber-columnize (2.1.1-1) unstable; urgency=medium + + * New upstream release. + * Added myself to uploaders. + * DH & compat to version 12. + * Build-Depends: golang-go --> golang-any + * Priority: optional; Standards-Version: 4.4.0. + + -- Dmitry Smirnov Thu, 26 Sep 2019 00:43:13 +1000 + golang-github-ryanuber-columnize (2.1.0-2) unstable; urgency=medium [ Paul Tagliamonte ] diff -Nru golang-github-ryanuber-columnize-2.1.0/debian/compat golang-github-ryanuber-columnize-2.1.1/debian/compat --- golang-github-ryanuber-columnize-2.1.0/debian/compat 2018-04-18 06:19:43.000000000 +0000 +++ golang-github-ryanuber-columnize-2.1.1/debian/compat 2019-09-25 14:42:33.000000000 +0000 @@ -1 +1 @@ -9 +12 diff -Nru golang-github-ryanuber-columnize-2.1.0/debian/control golang-github-ryanuber-columnize-2.1.1/debian/control --- golang-github-ryanuber-columnize-2.1.0/debian/control 2018-04-18 06:19:43.000000000 +0000 +++ golang-github-ryanuber-columnize-2.1.1/debian/control 2019-09-25 14:42:30.000000000 +0000 @@ -1,10 +1,12 @@ Source: golang-github-ryanuber-columnize Section: devel -Priority: extra +Priority: optional +Standards-Version: 4.4.0 Maintainer: Debian Go Packaging Team -Uploaders: Tianon Gravi , Tim Potter -Build-Depends: debhelper (>= 9), dh-golang, golang-go -Standards-Version: 3.9.6 +Uploaders: Tianon Gravi , + Tim Potter , + Dmitry Smirnov +Build-Depends: debhelper (>= 12~), dh-golang, golang-any Homepage: https://github.com/ryanuber/columnize Vcs-Browser: https://salsa.debian.org/go-team/packages/golang-github-ryanuber-columnize Vcs-Git: https://salsa.debian.org/go-team/packages/golang-github-ryanuber-columnize.git diff -Nru golang-github-ryanuber-columnize-2.1.0/debian/copyright golang-github-ryanuber-columnize-2.1.1/debian/copyright --- golang-github-ryanuber-columnize-2.1.0/debian/copyright 2018-04-18 06:19:43.000000000 +0000 +++ golang-github-ryanuber-columnize-2.1.1/debian/copyright 2019-09-25 14:42:56.000000000 +0000 @@ -3,7 +3,7 @@ Source: https://github.com/ryanuber/columnize Files: * -Copyright: 2014-2015 Ryan Uber +Copyright: 2014-2016 Ryan Uber License: Expat Files: debian/* diff -Nru golang-github-ryanuber-columnize-2.1.0/go.mod golang-github-ryanuber-columnize-2.1.1/go.mod --- golang-github-ryanuber-columnize-2.1.0/go.mod 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-ryanuber-columnize-2.1.1/go.mod 2019-03-19 23:35:15.000000000 +0000 @@ -0,0 +1 @@ +module github.com/ryanuber/columnize diff -Nru golang-github-ryanuber-columnize-2.1.0/LICENSE golang-github-ryanuber-columnize-2.1.1/LICENSE --- golang-github-ryanuber-columnize-2.1.0/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-ryanuber-columnize-2.1.1/LICENSE 2019-03-19 23:35:15.000000000 +0000 @@ -0,0 +1,20 @@ +Copyright (c) 2016 Ryan Uber + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff -Nru golang-github-ryanuber-columnize-2.1.0/README.md golang-github-ryanuber-columnize-2.1.1/README.md --- golang-github-ryanuber-columnize-2.1.0/README.md 2015-09-15 23:29:42.000000000 +0000 +++ golang-github-ryanuber-columnize-2.1.1/README.md 2019-03-19 23:35:15.000000000 +0000 @@ -4,6 +4,7 @@ Easy column-formatted output for golang [![Build Status](https://travis-ci.org/ryanuber/columnize.svg)](https://travis-ci.org/ryanuber/columnize) +[![GoDoc](https://godoc.org/github.com/ryanuber/columnize?status.svg)](https://godoc.org/github.com/ryanuber/columnize) Columnize is a really small Go package that makes building CLI's a little bit easier. In some CLI designs, you want to output a number similar items in a @@ -55,21 +56,16 @@ config.Glue = " " config.Prefix = "" config.Empty = "" +config.NoTrim = false ``` * `Delim` is the string by which columns of **input** are delimited * `Glue` is the string by which columns of **output** are delimited * `Prefix` is a string by which each line of **output** is prefixed * `Empty` is a string used to replace blank values found in output +* `NoTrim` is a boolean used to disable the automatic trimming of input values You can then pass the `Config` in using the `Format` method (signature below) to have text formatted to your liking. -Usage -===== - -```go -SimpleFormat(intput []string) string - -Format(input []string, config *Config) string -``` +See the [godoc](https://godoc.org/github.com/ryanuber/columnize) page for usage.