浏览代码

Implement stringutils.Ellipsis()

This patch implements an Ellipsis utility to
append an ellipsis (...)  when truncating
strings in output.

It also fixes the existing Truncate() utility
to be compatible with unicode/multibyte characters.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Sebastiaan van Stijn 9 年之前
父节点
当前提交
51dc35cf23

+ 2 - 2
api/client/formatter/container.go

@@ -124,7 +124,7 @@ func (c *containerContext) Command() string {
 	c.addHeader(commandHeader)
 	c.addHeader(commandHeader)
 	command := c.c.Command
 	command := c.c.Command
 	if c.trunc {
 	if c.trunc {
-		command = stringutils.Truncate(command, 20)
+		command = stringutils.Ellipsis(command, 20)
 	}
 	}
 	return strconv.Quote(command)
 	return strconv.Quote(command)
 }
 }
@@ -200,7 +200,7 @@ func (c *containerContext) Mounts() string {
 			name = m.Name
 			name = m.Name
 		}
 		}
 		if c.trunc {
 		if c.trunc {
-			name = stringutils.Truncate(name, 15)
+			name = stringutils.Ellipsis(name, 15)
 		}
 		}
 		mounts = append(mounts, name)
 		mounts = append(mounts, name)
 	}
 	}

+ 2 - 2
api/client/formatter/container_test.go

@@ -60,12 +60,12 @@ func TestContainerPsContext(t *testing.T) {
 		{types.Container{
 		{types.Container{
 			Mounts: []types.MountPoint{
 			Mounts: []types.MountPoint{
 				{
 				{
-					Name:   "733908409c91817de8e92b0096373245f329f19a88e2c849f02460e9b3d1c203",
+					Name:   "this-is-a-long-volume-name-and-will-be-truncated-if-trunc-is-set",
 					Driver: "local",
 					Driver: "local",
 					Source: "/a/path",
 					Source: "/a/path",
 				},
 				},
 			},
 			},
-		}, true, "733908409c91817", mountsHeader, ctx.Mounts},
+		}, true, "this-is-a-lo...", mountsHeader, ctx.Mounts},
 		{types.Container{
 		{types.Container{
 			Mounts: []types.MountPoint{
 			Mounts: []types.MountPoint{
 				{
 				{

+ 2 - 2
api/client/image/history.go

@@ -79,8 +79,8 @@ func runHistory(dockerCli *client.DockerCli, opts historyOptions) error {
 	for _, entry := range history {
 	for _, entry := range history {
 		imageID = entry.ID
 		imageID = entry.ID
 		createdBy = strings.Replace(entry.CreatedBy, "\t", " ", -1)
 		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)
 			imageID = stringid.TruncateID(entry.ID)
 		}
 		}
 
 

+ 2 - 2
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(res.Description, "\n", " ", -1)
 		desc = strings.Replace(desc, "\r", " ", -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)
 		fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount)
 		if res.IsOfficial {
 		if res.IsOfficial {

+ 2 - 2
api/client/plugin/list.go

@@ -51,8 +51,8 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error {
 	for _, p := range plugins {
 	for _, p := range plugins {
 		desc := strings.Replace(p.Manifest.Description, "\n", " ", -1)
 		desc := strings.Replace(p.Manifest.Description, "\n", " ", -1)
 		desc = strings.Replace(desc, "\r", " ", -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)
 		fmt.Fprintf(w, "%s\t%s\t%s\t%v\n", p.Name, p.Tag, desc, p.Active)

+ 16 - 2
pkg/stringutils/stringutils.go

@@ -32,12 +32,26 @@ func GenerateRandomASCIIString(n int) string {
 	return string(res)
 	return string(res)
 }
 }
 
 
+// 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
+	}
+	if maxlen <= 3 {
+		return string(r[:maxlen])
+	}
+	return string(r[:maxlen-3]) + "..."
+}
+
 // Truncate truncates a string to maxlen.
 // Truncate truncates a string to maxlen.
 func Truncate(s string, maxlen int) string {
 func Truncate(s string, maxlen int) string {
-	if len(s) <= maxlen {
+	r := []rune(s)
+	if len(r) <= maxlen {
 		return s
 		return s
 	}
 	}
-	return s[:maxlen]
+	return string(r[:maxlen])
 }
 }
 
 
 // InSlice tests whether a string is contained in a slice of strings or not.
 // InSlice tests whether a string is contained in a slice of strings or not.

+ 24 - 8
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) {
 func TestTruncate(t *testing.T) {
-	str := "teststring"
+	str := "t🐳ststring"
 	newstr := Truncate(str, 4)
 	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)
 	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) {
 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 {
 	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")
 	test = InSlice(slice, "SLICE")
 	if !test {
 	if !test {