From 94cdae5247e2e2a6baf24113bbe6836b92cc3ef9 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Thu, 23 Nov 2023 14:39:21 +0100 Subject: [PATCH] ls: no-trunc opt Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- commands/ls.go | 83 +++++++++++++++++++++++++++++++++---- commands/ls_test.go | 72 ++++++++++++++++++++++++++++++++ docs/reference/buildx_ls.md | 7 ++-- 3 files changed, 151 insertions(+), 11 deletions(-) create mode 100644 commands/ls_test.go diff --git a/commands/ls.go b/commands/ls.go index 0be95f7ab04c..15faf73ac743 100644 --- a/commands/ls.go +++ b/commands/ls.go @@ -36,7 +36,8 @@ const ( ) type lsOptions struct { - format string + format string + noTrunc bool } func runLs(dockerCli command.Cli, in lsOptions) error { @@ -75,7 +76,7 @@ func runLs(dockerCli command.Cli, in lsOptions) error { return err } - if hasErrors, err := lsPrint(dockerCli, current, builders, in.format); err != nil { + if hasErrors, err := lsPrint(dockerCli, current, builders, in); err != nil { return err } else if hasErrors { _, _ = fmt.Fprintf(dockerCli.Err(), "\n") @@ -110,6 +111,7 @@ func lsCmd(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() flags.StringVar(&options.format, "format", formatter.TableFormatKey, "Format the output") + flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output") // hide builder persistent flag for this command cobrautil.HideInheritedFlags(cmd, "builder") @@ -117,14 +119,15 @@ func lsCmd(dockerCli command.Cli) *cobra.Command { return cmd } -func lsPrint(dockerCli command.Cli, current *store.NodeGroup, builders []*builder.Builder, format string) (hasErrors bool, _ error) { - if format == formatter.TableFormatKey { - format = lsDefaultTableFormat +func lsPrint(dockerCli command.Cli, current *store.NodeGroup, builders []*builder.Builder, in lsOptions) (hasErrors bool, _ error) { + if in.format == formatter.TableFormatKey { + in.format = lsDefaultTableFormat } ctx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.Format(format), + Format: formatter.Format(in.format), + Trunc: !in.noTrunc, } sort.SliceStable(builders, func(i, j int) bool { @@ -141,11 +144,12 @@ func lsPrint(dockerCli command.Cli, current *store.NodeGroup, builders []*builde render := func(format func(subContext formatter.SubContext) error) error { for _, b := range builders { if err := format(&lsContext{ + format: ctx.Format, + trunc: ctx.Trunc, Builder: &lsBuilder{ Builder: b, Current: b.Name == current.Name, }, - format: ctx.Format, }); err != nil { return err } @@ -163,6 +167,7 @@ func lsPrint(dockerCli command.Cli, current *store.NodeGroup, builders []*builde } if err := format(&lsContext{ format: ctx.Format, + trunc: ctx.Trunc, Builder: &lsBuilder{ Builder: b, Current: b.Name == current.Name, @@ -199,6 +204,7 @@ type lsContext struct { Builder *lsBuilder format formatter.Format + trunc bool node builder.Node } @@ -264,7 +270,16 @@ func (c *lsContext) Platforms() string { if c.node.Name == "" { return "" } - return strings.Join(platformutil.FormatInGroups(c.node.Node.Platforms, c.node.Platforms), ", ") + platforms := platformutil.FormatInGroups(c.node.Node.Platforms, c.node.Platforms) + if c.trunc && c.format.IsTable() { + tplatforms, truncated := truncPlatforms(platforms, 4) + out := strings.Join(tplatforms, ", ") + if truncated { + out += " …" + } + return out + } + return strings.Join(platforms, ", ") } func (c *lsContext) Error() string { @@ -275,3 +290,55 @@ func (c *lsContext) Error() string { } return "" } + +func truncPlatforms(platforms []string, max int) ([]string, bool) { + majorPlatforms := map[string]struct{}{ + "linux/amd64": {}, + "linux/arm64": {}, + "linux/arm/v7": {}, + "linux/mips64": {}, + "linux/ppc64le": {}, + "linux/riscv64": {}, + "linux/s390x": {}, + } + var res []string + var left []string + m := map[string]struct{}{} + for _, p := range platforms { + if len(res) >= max { + break + } + pp := strings.TrimSuffix(p, "*") + if _, ok := m[pp]; ok { + continue + } + if _, ok := majorPlatforms[pp]; ok { + res = append(res, p) + m[pp] = struct{}{} + } else { + left = append(left, p) + } + } + if len(left) > 0 && len(res) < max { + cl := max - len(res) + if cl > len(left) { + cl = len(left) + } + res = append(res, left[:cl]...) + } + sort.SliceStable(res, func(i, j int) bool { + ipref := strings.HasSuffix(res[i], "*") + jpref := strings.HasSuffix(res[j], "*") + if ipref && !jpref { + return true + } else if !ipref && jpref { + return false + } + return i < j + }) + var truncated bool + if len(platforms) > max && len(res) < len(platforms) { + truncated = true + } + return res, truncated +} diff --git a/commands/ls_test.go b/commands/ls_test.go new file mode 100644 index 000000000000..923527ffd727 --- /dev/null +++ b/commands/ls_test.go @@ -0,0 +1,72 @@ +package commands + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTruncPlatforms(t *testing.T) { + tests := []struct { + name string + platforms []string + expected []string + truncated bool + max int + }{ + { + name: "arm64 preferred and emulated", + platforms: []string{"linux/arm64*", "linux/amd64", "linux/amd64/v2", "linux/riscv64", "linux/ppc64le", "linux/s390x", "linux/386", "linux/mips64le", "linux/mips64", "linux/arm/v7", "linux/arm/v6"}, + expected: []string{"linux/arm64*", "linux/amd64", "linux/riscv64", "linux/ppc64le"}, + truncated: true, + max: 4, + }, + { + name: "riscv64 preferred only", + platforms: []string{"linux/riscv64*"}, + expected: []string{"linux/riscv64*"}, + max: 4, + }, + { + name: "amd64 no preferred and emulated", + platforms: []string{"linux/amd64", "linux/amd64/v2", "linux/amd64/v3", "linux/386", "linux/arm64", "linux/riscv64", "linux/ppc64le", "linux/s390x", "linux/mips64le", "linux/mips64", "linux/arm/v7", "linux/arm/v6"}, + expected: []string{"linux/amd64", "linux/arm64", "linux/riscv64", "linux/ppc64le"}, + truncated: true, + max: 4, + }, + { + name: "amd64 no preferred", + platforms: []string{"linux/amd64", "linux/386"}, + expected: []string{"linux/amd64", "linux/386"}, + max: 4, + }, + { + name: "arm64 no preferred", + platforms: []string{"linux/arm64", "linux/arm/v7", "linux/arm/v6"}, + expected: []string{"linux/arm64", "linux/arm/v7", "linux/arm/v6"}, + max: 4, + }, + { + name: "all preferred", + platforms: []string{"darwin/arm64*", "linux/arm64*", "linux/arm/v5*", "linux/arm/v6*", "linux/arm/v7*", "windows/arm64*"}, + expected: []string{"linux/arm64*", "linux/arm/v7*", "darwin/arm64*", "linux/arm/v5*"}, + truncated: true, + max: 4, + }, + { + name: "no major preferred", + platforms: []string{"linux/amd64/v2*", "linux/arm/v6*", "linux/mips64le*", "linux/amd64", "linux/amd64/v3", "linux/386", "linux/arm64", "linux/riscv64", "linux/ppc64le", "linux/s390x", "linux/mips64", "linux/arm/v7"}, + expected: []string{"linux/amd64", "linux/arm64", "linux/riscv64", "linux/ppc64le"}, + truncated: true, + max: 4, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + tplatforms, truncated := truncPlatforms(tt.platforms, tt.max) + assert.Equal(t, tt.expected, tplatforms) + assert.Equal(t, tt.truncated, truncated) + }) + } +} diff --git a/docs/reference/buildx_ls.md b/docs/reference/buildx_ls.md index 988d1144930c..d755d2bda548 100644 --- a/docs/reference/buildx_ls.md +++ b/docs/reference/buildx_ls.md @@ -9,9 +9,10 @@ List builder instances ### Options -| Name | Type | Default | Description | -|:----------------------|:---------|:--------|:------------------| -| [`--format`](#format) | `string` | `table` | Format the output | +| Name | Type | Default | Description | +|:----------------------|:---------|:--------|:----------------------| +| [`--format`](#format) | `string` | `table` | Format the output | +| `--no-trunc` | | | Don't truncate output |