diff --git a/api/client/formatter/container.go b/api/client/formatter/container.go index 4903b4ef80..5b96f607b3 100644 --- a/api/client/formatter/container.go +++ b/api/client/formatter/container.go @@ -124,7 +124,7 @@ func (c *containerContext) Command() string { c.addHeader(commandHeader) command := c.c.Command if c.trunc { - command = stringutils.Truncate(command, 20) + command = stringutils.Ellipsis(command, 20) } return strconv.Quote(command) } @@ -200,7 +200,7 @@ func (c *containerContext) Mounts() string { name = m.Name } if c.trunc { - name = stringutils.Truncate(name, 15) + name = stringutils.Ellipsis(name, 15) } mounts = append(mounts, name) } diff --git a/api/client/formatter/container_test.go b/api/client/formatter/container_test.go index c5f45ef6ff..4776123be4 100644 --- a/api/client/formatter/container_test.go +++ b/api/client/formatter/container_test.go @@ -60,12 +60,12 @@ func TestContainerPsContext(t *testing.T) { {types.Container{ Mounts: []types.MountPoint{ { - Name: "733908409c91817de8e92b0096373245f329f19a88e2c849f02460e9b3d1c203", + Name: "this-is-a-long-volume-name-and-will-be-truncated-if-trunc-is-set", Driver: "local", Source: "/a/path", }, }, - }, true, "733908409c91817", mountsHeader, ctx.Mounts}, + }, true, "this-is-a-lo...", mountsHeader, ctx.Mounts}, {types.Container{ Mounts: []types.MountPoint{ { diff --git a/api/client/image/history.go b/api/client/image/history.go index abf2a0bb3b..5ead8fd2af 100644 --- a/api/client/image/history.go +++ b/api/client/image/history.go @@ -79,8 +79,8 @@ func runHistory(dockerCli *client.DockerCli, opts historyOptions) error { for _, entry := range history { imageID = entry.ID createdBy = strings.Replace(entry.CreatedBy, "\t", " ", -1) - if opts.noTrunc == false { - createdBy = stringutils.Truncate(createdBy, 45) + if !opts.noTrunc { + createdBy = stringutils.Ellipsis(createdBy, 45) imageID = stringid.TruncateID(entry.ID) } diff --git a/api/client/image/search.go b/api/client/image/search.go index d42b8aaf6d..8023a0732d 100644 --- a/api/client/image/search.go +++ b/api/client/image/search.go @@ -109,8 +109,8 @@ func runSearch(dockerCli *client.DockerCli, opts searchOptions) error { } desc := strings.Replace(res.Description, "\n", " ", -1) desc = strings.Replace(desc, "\r", " ", -1) - if !opts.noTrunc && len(desc) > 45 { - desc = stringutils.Truncate(desc, 42) + "..." + if !opts.noTrunc { + desc = stringutils.Ellipsis(desc, 45) } fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount) if res.IsOfficial { diff --git a/api/client/plugin/list.go b/api/client/plugin/list.go index daa6492f3c..8ff16f984f 100644 --- a/api/client/plugin/list.go +++ b/api/client/plugin/list.go @@ -51,8 +51,8 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error { for _, p := range plugins { desc := strings.Replace(p.Manifest.Description, "\n", " ", -1) desc = strings.Replace(desc, "\r", " ", -1) - if !opts.noTrunc && len(desc) > 45 { - desc = stringutils.Truncate(desc, 42) + "..." + if !opts.noTrunc { + desc = stringutils.Ellipsis(desc, 45) } fmt.Fprintf(w, "%s\t%s\t%s\t%v\n", p.Name, p.Tag, desc, p.Active) diff --git a/pkg/stringutils/stringutils.go b/pkg/stringutils/stringutils.go index 7c00b972dd..8e1c812d7a 100644 --- a/pkg/stringutils/stringutils.go +++ b/pkg/stringutils/stringutils.go @@ -32,12 +32,26 @@ func GenerateRandomASCIIString(n int) string { return string(res) } -// Truncate truncates a string to maxlen. -func Truncate(s string, maxlen int) string { - if len(s) <= maxlen { +// Ellipsis truncates a string to fit within maxlen, and appends ellipsis (...). +// For maxlen of 3 and lower, no ellipsis is appended. +func Ellipsis(s string, maxlen int) string { + r := []rune(s) + if len(r) <= maxlen { return s } - return s[:maxlen] + if maxlen <= 3 { + return string(r[:maxlen]) + } + return string(r[:maxlen-3]) + "..." +} + +// Truncate truncates a string to maxlen. +func Truncate(s string, maxlen int) string { + r := []rune(s) + if len(r) <= maxlen { + return s + } + return string(r[:maxlen]) } // InSlice tests whether a string is contained in a slice of strings or not. diff --git a/pkg/stringutils/stringutils_test.go b/pkg/stringutils/stringutils_test.go index fec59450bc..8af2bdcc0b 100644 --- a/pkg/stringutils/stringutils_test.go +++ b/pkg/stringutils/stringutils_test.go @@ -57,24 +57,40 @@ func TestGenerateRandomAsciiStringIsAscii(t *testing.T) { } } +func TestEllipsis(t *testing.T) { + str := "t🐳ststring" + newstr := Ellipsis(str, 3) + if newstr != "t🐳s" { + t.Fatalf("Expected t🐳s, got %s", newstr) + } + newstr = Ellipsis(str, 8) + if newstr != "t🐳sts..." { + t.Fatalf("Expected tests..., got %s", newstr) + } + newstr = Ellipsis(str, 20) + if newstr != "t🐳ststring" { + t.Fatalf("Expected t🐳ststring, got %s", newstr) + } +} + func TestTruncate(t *testing.T) { - str := "teststring" + str := "t🐳ststring" newstr := Truncate(str, 4) - if newstr != "test" { - t.Fatalf("Expected test, got %s", newstr) + if newstr != "t🐳st" { + t.Fatalf("Expected t🐳st, got %s", newstr) } newstr = Truncate(str, 20) - if newstr != "teststring" { - t.Fatalf("Expected teststring, got %s", newstr) + if newstr != "t🐳ststring" { + t.Fatalf("Expected t🐳ststring, got %s", newstr) } } func TestInSlice(t *testing.T) { - slice := []string{"test", "in", "slice"} + slice := []string{"t🐳st", "in", "slice"} - test := InSlice(slice, "test") + test := InSlice(slice, "t🐳st") if !test { - t.Fatalf("Expected string test to be in slice") + t.Fatalf("Expected string t🐳st to be in slice") } test = InSlice(slice, "SLICE") if !test {